diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index e3e110127..d1d60e3fa 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,6 +1,15 @@
+---
version: 2
updates:
- package-ecosystem: "pip"
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
+ versioning-strategy: "increase"
+ ignore:
+ - dependency-name: "*"
+ update-types: ["version-update:semver-patch"]
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "monthly"
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index a4a65789b..dbcdd3bee 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -3,19 +3,22 @@ name: CI
on:
- push
- pull_request
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
jobs:
lint:
name: Run linters
runs-on: ubuntu-latest
steps:
- name: Checkout source code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
- python-version: "3.12"
+ python-version: "3.13"
- name: Set up Go
- uses: actions/setup-go@v4
+ uses: actions/setup-go@v5
with:
go-version: "1.21"
- name: Install dependencies
@@ -29,7 +32,7 @@ jobs:
matrix:
# NOTE: If you add a version here, don't forget to update the
# '[gh-actions]' section in tox.ini
- python: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+ python: ["3.9", "3.10", "3.11", "3.12", "3.13"]
db: [postgres, mysql, sqlite3]
env:
DATABASE_TYPE: "${{ matrix.db }}"
@@ -68,9 +71,9 @@ jobs:
--health-retries 5
steps:
- name: Checkout source code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: Install Python dependencies
@@ -99,19 +102,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
- python-version: "3.12"
+ python-version: "3.13"
- name: Install dependencies
run: python -m pip install tox
- name: Build docs (via tox)
run: tox -e docs
- name: Archive build results
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: html-docs-build
path: docs/_build/html
@@ -126,39 +129,39 @@ jobs:
COMPOSE_FILE: ${{ matrix.db == 'mysql' && 'docker-compose.yml' || (matrix.db == 'postgres' && 'docker-compose-pg.yml') || 'docker-compose-sqlite3.yml' }}
steps:
- name: Checkout source code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
- python-version: "3.12"
+ python-version: "3.13"
- name: Build docker-compose service
run: |
- docker-compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g)
+ docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g)
- name: Test createsuperuser/changepassword
run: |
- docker-compose run -T --rm web \
+ docker compose run -T --rm web \
python manage.py createsuperuser \
--username patchwork --no-input --email test@example.com
{ echo patchwork; echo patchwork; } | \
- docker-compose run -T --rm web \
+ docker compose run -T --rm web \
python manage.py changepassword patchwork
# FIXME(stephenfin): Re-enable this once dbbackup supports Django 4.0
# - name: Test dbbackup/dbrestore
# run: |
- # docker-compose run -T --rm web python manage.py dbbackup
- # echo y | docker-compose run -T --rm web python manage.py dbrestore
+ # docker compose run -T --rm web python manage.py dbbackup
+ # echo y | docker compose run -T --rm web python manage.py dbrestore
- name: Modify database user permissions (mysql)
if: ${{ matrix.db == 'mysql' }}
run: |
- docker-compose exec -T -- db \
+ docker compose exec -T -- db \
sh -c "exec mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"GRANT ALL ON \\\`test\\_\${MYSQL_DATABASE}%\\\`.* to '\${MYSQL_USER}'@'%'; FLUSH PRIVILEGES;\""
- name: Run unittest
- run: docker-compose run -T --rm web tox
+ run: docker compose run -T --rm web tox
- name: Test normal startup
run: |
- docker-compose up --detach
+ docker compose up --detach
for count in $(seq 50); do \
if curl --fail --silent "http://localhost:8000"; then \
@@ -168,7 +171,7 @@ jobs:
done
echo
- docker-compose ps
+ docker compose ps
- name: Test client access (git-pw)
run: |
python -m pip install git-pw
@@ -177,4 +180,4 @@ jobs:
--username patchwork --password patchwork series list
- name: Dump container logs
if: ${{ always() }}
- run: docker-compose logs --no-color --timestamps
+ run: docker compose logs --no-color --timestamps
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 367d7e05c..d67cf098e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,10 +1,7 @@
---
-default_language_version:
- # force all unspecified python hooks to run python3
- python: python3
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.5.0
+ rev: v5.0.0
hooks:
- id: check-executables-have-shebangs
- id: check-merge-conflict
@@ -14,7 +11,7 @@ repos:
- id: trailing-whitespace
exclude: (.*\.mbox)|(.*\.svg)
- repo: https://github.com/Lucas-C/pre-commit-hooks
- rev: v1.5.4
+ rev: v1.5.5
hooks:
- id: remove-tabs
exclude: (.*\.mbox)|(.*\.svg)|(.*\.sql)|(.*\.conf)
@@ -22,7 +19,7 @@ repos:
exclude: (.*\.mbox)|(.*\.svg)
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
- rev: v0.1.13
+ rev: v0.7.0
hooks:
# Run the linter.
- id: ruff
@@ -33,7 +30,7 @@ repos:
hooks:
- id: bashate
- repo: https://github.com/daveshanley/vacuum
- rev: v0.7.2
+ rev: v0.12.1
hooks:
- id: vacuum
files: ^docs/api/schemas/(latest|v\d\.\d)/patchwork.yaml
diff --git a/README.rst b/README.rst
index 979801fb3..ebde1f363 100644
--- a/README.rst
+++ b/README.rst
@@ -85,11 +85,14 @@ environment. To install Patchwork:
$ git clone https://github.com/getpatchwork/patchwork.git
-3. Create a ``.env`` file in the root directory of the project and store your
- ``UID`` and ``GID`` attributes there::
+3. (Optional) Create a ``.env`` file in the root directory of the project and
+ store your ``UID`` and ``GID`` attributes there::
$ cd patchwork && printf "UID=$(id -u)\nGID=$(id -g)\n" > .env
+ This should only be necessary if you have a ``UID`` or ``GID`` other than
+ ``1000``.
+
4. Build the images. This will download a number of packages from the internet,
and compile several versions of Python::
diff --git a/docker-compose-pg.yml b/docker-compose-pg.yml
index 645f93e16..44bc3ec0a 100644
--- a/docker-compose-pg.yml
+++ b/docker-compose-pg.yml
@@ -1,4 +1,4 @@
-version: "3"
+---
services:
db:
image: postgres:latest
diff --git a/docker-compose-sqlite3.yml b/docker-compose-sqlite3.yml
index d4c66593f..900cb71fd 100644
--- a/docker-compose-sqlite3.yml
+++ b/docker-compose-sqlite3.yml
@@ -1,4 +1,4 @@
-version: "3"
+---
services:
web:
build:
diff --git a/docker-compose.yml b/docker-compose.yml
index 9bff93f65..73f080a49 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,4 +1,4 @@
-version: "3"
+---
services:
db:
image: mysql:latest
diff --git a/docs/api/rest/index.rst b/docs/api/rest/index.rst
index 67022e6f3..97095d13e 100644
--- a/docs/api/rest/index.rst
+++ b/docs/api/rest/index.rst
@@ -8,7 +8,7 @@ This guide provides an overview of how one can interact with the REST API. For
detailed information on type and response format of the various resources
exposed by the API, refer to the web browsable API. This can be found at:
- https://patchwork.example.com/api/1.3/
+ https://patchwork.example.com/api/1.4/
where `patchwork.example.com` refers to the URL of your Patchwork instance.
@@ -43,6 +43,11 @@ If all you want is reference guides, skip straight to :ref:`rest-api-schemas`.
The API version was bumped to v1.3 in Patchwork v3.1. The older APIs are
still supported. For more information, refer to :ref:`rest-api-versions`.
+.. versionchanged:: 3.2
+
+ The API version was bumped to v1.4 in Patchwork v3.2. The older APIs are
+ still supported. For more information, refer to :ref:`rest-api-versions`.
+
Getting Started
---------------
@@ -57,16 +62,16 @@ Patchwork instance hosted at `patchwork.example.com`, run:
.. code-block:: shell
- $ curl -s 'https://patchwork.example.com/api/1.3/' | python -m json.tool
+ $ curl -s 'https://patchwork.example.com/api/1.4/' | python -m json.tool
{
- "bundles": "https://patchwork.example.com/api/1.3/bundles/",
- "covers": "https://patchwork.example.com/api/1.3/covers/",
- "events": "https://patchwork.example.com/api/1.3/events/",
- "patches": "https://patchwork.example.com/api/1.3/patches/",
- "people": "https://patchwork.example.com/api/1.3/people/",
- "projects": "https://patchwork.example.com/api/1.3/projects/",
- "series": "https://patchwork.example.com/api/1.3/series/",
- "users": "https://patchwork.example.com/api/1.3/users/"
+ "bundles": "https://patchwork.example.com/api/1.4/bundles/",
+ "covers": "https://patchwork.example.com/api/1.4/covers/",
+ "events": "https://patchwork.example.com/api/1.4/events/",
+ "patches": "https://patchwork.example.com/api/1.4/patches/",
+ "people": "https://patchwork.example.com/api/1.4/people/",
+ "projects": "https://patchwork.example.com/api/1.4/projects/",
+ "series": "https://patchwork.example.com/api/1.4/series/",
+ "users": "https://patchwork.example.com/api/1.4/users/"
}
@@ -79,17 +84,17 @@ well-supported. To repeat the above example using `requests`:, run
$ python
>>> import json
>>> import requests
- >>> r = requests.get('https://patchwork.example.com/api/1.3/')
+ >>> r = requests.get('https://patchwork.example.com/api/1.4/')
>>> print(json.dumps(r.json(), indent=2))
{
- "bundles": "https://patchwork.example.com/api/1.3/bundles/",
- "covers": "https://patchwork.example.com/api/1.3/covers/",
- "events": "https://patchwork.example.com/api/1.3/events/",
- "patches": "https://patchwork.example.com/api/1.3/patches/",
- "people": "https://patchwork.example.com/api/1.3/people/",
- "projects": "https://patchwork.example.com/api/1.3/projects/",
- "series": "https://patchwork.example.com/api/1.3/series/",
- "users": "https://patchwork.example.com/api/1.3/users/"
+ "bundles": "https://patchwork.example.com/api/1.4/bundles/",
+ "covers": "https://patchwork.example.com/api/1.4/covers/",
+ "events": "https://patchwork.example.com/api/1.4/events/",
+ "patches": "https://patchwork.example.com/api/1.4/patches/",
+ "people": "https://patchwork.example.com/api/1.4/people/",
+ "projects": "https://patchwork.example.com/api/1.4/projects/",
+ "series": "https://patchwork.example.com/api/1.4/series/",
+ "users": "https://patchwork.example.com/api/1.4/users/"
}
Tools like `curl` and libraries like `requests` can be used to build anything
@@ -108,7 +113,7 @@ Versioning
----------
By default, all requests will receive the latest version of the API: currently
-``1.3``:
+``1.4``:
.. code-block:: http
@@ -119,7 +124,7 @@ changes breaking your application:
.. code-block:: http
- GET /api/1.3 HTTP/1.1
+ GET /api/1.4 HTTP/1.1
Older API versions will be deprecated and removed over time. For more
information, refer to :ref:`rest-api-versions`.
@@ -275,6 +280,7 @@ Supported Versions
1.1, 2.1, ✓
1.2, 2.2, ✓
1.3, 3.1, ✓
+ 1.4, 3.2, ✓
Further information about this and more can typically be found in
:doc:`the release notes `.
@@ -292,6 +298,7 @@ Auto-generated schema documentation is provided below.
/api/rest/schemas/v1.1
/api/rest/schemas/v1.2
/api/rest/schemas/v1.3
+ /api/rest/schemas/v1.4
.. Links
diff --git a/docs/api/rest/schemas/v1.3.rst b/docs/api/rest/schemas/v1.3.rst
index 17a4421ae..6bbf1a560 100644
--- a/docs/api/rest/schemas/v1.3.rst
+++ b/docs/api/rest/schemas/v1.3.rst
@@ -1,5 +1,5 @@
-API v1.3 (latest)
-=================
+API v1.3
+========
.. openapi:: ../../schemas/v1.3/patchwork.yaml
:examples:
diff --git a/docs/api/rest/schemas/v1.4.rst b/docs/api/rest/schemas/v1.4.rst
new file mode 100644
index 000000000..11e34f6a5
--- /dev/null
+++ b/docs/api/rest/schemas/v1.4.rst
@@ -0,0 +1,5 @@
+API v1.4 (latest)
+=================
+
+.. openapi:: ../../schemas/v1.4/patchwork.yaml
+ :examples:
diff --git a/docs/api/schemas/generate-schemas.py b/docs/api/schemas/generate-schemas.py
index 14b741473..52008dffd 100755
--- a/docs/api/schemas/generate-schemas.py
+++ b/docs/api/schemas/generate-schemas.py
@@ -14,8 +14,8 @@
yaml = None
ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
-VERSIONS = [(1, 0), (1, 1), (1, 2), (1, 3), None]
-LATEST_VERSION = (1, 3)
+VERSIONS = [(1, 0), (1, 1), (1, 2), (1, 3), (1, 4), None]
+LATEST_VERSION = (1, 4)
def generate_schemas():
diff --git a/docs/api/schemas/latest/patchwork.yaml b/docs/api/schemas/latest/patchwork.yaml
index 93e56fa0f..ae2c7e9cf 100644
--- a/docs/api/schemas/latest/patchwork.yaml
+++ b/docs/api/schemas/latest/patchwork.yaml
@@ -13,7 +13,7 @@ info:
license:
name: GPL v2 License
url: https://www.gnu.org/licenses/gpl-2.0.html
- version: '1.3'
+ version: '1.4'
paths:
/api:
get:
@@ -816,6 +816,190 @@ paths:
$ref: '#/components/schemas/Error'
tags:
- comments
+ /api/patches/{patch_id}/notes/{note_id}:
+ parameters:
+ - in: path
+ name: patch_id
+ description: A unique integer value identifying the parent patch.
+ required: true
+ schema:
+ title: Patch ID
+ type: integer
+ - in: path
+ name: note_id
+ description: A unique integer value identifying the note.
+ required: true
+ schema:
+ title: Note ID
+ type: integer
+ get:
+ summary: Retrieve a patch note.
+ description: |
+ Retrieve note from a patch by the ID of the patch.
+ operationId: patch_notes_read
+ responses:
+ '200':
+ description: 'A patch note'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ patch:
+ summary: Update a patch note.
+ description:
+ Partially update an existing patch note.
+ You must be a maintainer of the project that the patch note belongs to.
+ operationId: patch_notes_update
+ requestBody:
+ $ref: '#/components/requestBodies/Note'
+ responses:
+ '200':
+ description: 'Updated note'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorNoteUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ delete:
+ summary: Delete a patch note.
+ description:
+ Delete an existing patch note.
+ You must be a maintainer of the project that the patch note belongs to.
+ operationId: patch_notes_delete
+ requestBody:
+ $ref: '#/components/requestBodies/Note'
+ responses:
+ '204':
+ description: 'Delete note'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorNoteUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ /api/patches/{patch_id}/notes:
+ parameters:
+ - in: path
+ name: patch_id
+ description: A unique integer value identifying the parent patch.
+ required: true
+ schema:
+ title: Patch ID
+ type: integer
+ post:
+ summary: Create a patch note.
+ description: |
+ Create note for a patch.
+ operationId: patch_notes_create
+ responses:
+ '200':
+ description: 'A patch note'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '201':
+ description: 'Created'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '400':
+ description: 'Bad request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ get:
+ summary: Retrieve all notes from a patch.
+ description: |
+ Retrieve all note from a specific patch.
+ operationId: patch_notes_list
+ responses:
+ '200':
+ description: 'A list of patch notes'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Note'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+
/api/patches/{patch_id}/checks:
parameters:
- in: path
@@ -1478,6 +1662,14 @@ components:
application/json:
schema:
$ref: '#/components/schemas/CommentUpdate'
+ Note:
+ required: true
+ description: |
+ A patch note.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/NoteUpdate'
Patch:
required: true
description: |
@@ -2170,6 +2362,43 @@ components:
$ref: '#/components/schemas/PatchEmbedded'
comment:
$ref: '#/components/schemas/CommentEmbedded'
+ Note:
+ type: object
+ title: Note
+ description: |
+ A note
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ created_at:
+ title: CreatedAt
+ type: string
+ format: iso8601
+ readOnly: true
+ updated_at:
+ title: UpdatedAt
+ type: string
+ format: iso8601
+ readOnly: true
+ submitter:
+ type: object
+ title: Submitter
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/PersonEmbedded'
+ content:
+ title: Content
+ type: string
+ readOnly: true
+ minLength: 1
+ NoteUpdate:
+ type: object
+ properties:
+ addressed:
+ title: Addressed
+ type: boolean
PatchList:
required:
- state
@@ -3134,6 +3363,17 @@ components:
type: array
items:
type: string
+ ErrorNoteUpdate:
+ type: object
+ title: A note update error.
+ description: |
+ A mapping of field names to validation failures.
+ properties:
+ addressed:
+ title: Addressed
+ type: array
+ items:
+ type: string
ErrorPatchUpdate:
type: object
title: A patch update error.
@@ -3210,6 +3450,8 @@ tags:
description: Series operations
- name: comments
description: Comment operations
+ - name: notes
+ description: Note operations
- name: people
description: Submitter operations
- name: users
diff --git a/docs/api/schemas/patchwork.j2 b/docs/api/schemas/patchwork.j2
index 516fbe88d..a2928c229 100644
--- a/docs/api/schemas/patchwork.j2
+++ b/docs/api/schemas/patchwork.j2
@@ -840,6 +840,192 @@ paths:
$ref: '#/components/schemas/Error'
tags:
- comments
+{% endif %}
+{% if version >= (1, 4) %}
+ /api/{{ version_url }}patches/{patch_id}/notes/{note_id}:
+ parameters:
+ - in: path
+ name: patch_id
+ description: A unique integer value identifying the parent patch.
+ required: true
+ schema:
+ title: Patch ID
+ type: integer
+ - in: path
+ name: note_id
+ description: A unique integer value identifying the note.
+ required: true
+ schema:
+ title: Note ID
+ type: integer
+ get:
+ summary: Retrieve a patch note.
+ description: |
+ Retrieve note from a patch by the ID of the patch.
+ operationId: patch_notes_read
+ responses:
+ '200':
+ description: 'A patch note'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ patch:
+ summary: Update a patch note.
+ description:
+ Partially update an existing patch note.
+ You must be a maintainer of the project that the patch note belongs to.
+ operationId: patch_notes_update
+ requestBody:
+ $ref: '#/components/requestBodies/Note'
+ responses:
+ '200':
+ description: 'Updated note'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorNoteUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ delete:
+ summary: Delete a patch note.
+ description:
+ Delete an existing patch note.
+ You must be a maintainer of the project that the patch note belongs to.
+ operationId: patch_notes_delete
+ requestBody:
+ $ref: '#/components/requestBodies/Note'
+ responses:
+ '204':
+ description: 'Delete note'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorNoteUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ /api/{{ version_url }}patches/{patch_id}/notes:
+ parameters:
+ - in: path
+ name: patch_id
+ description: A unique integer value identifying the parent patch.
+ required: true
+ schema:
+ title: Patch ID
+ type: integer
+ post:
+ summary: Create a patch note.
+ description: |
+ Create note for a patch.
+ operationId: patch_notes_create
+ responses:
+ '200':
+ description: 'A patch note'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '201':
+ description: 'Created'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '400':
+ description: 'Bad request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ get:
+ summary: Retrieve all notes from a patch.
+ description: |
+ Retrieve all note from a specific patch.
+ operationId: patch_notes_list
+ responses:
+ '200':
+ description: 'A list of patch notes'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Note'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+
{% endif %}
/api/{{ version_url }}patches/{patch_id}/checks:
parameters:
@@ -1518,6 +1704,16 @@ components:
application/json:
schema:
$ref: '#/components/schemas/CommentUpdate'
+{% endif %}
+{% if version >= (1, 4) %}
+ Note:
+ required: true
+ description: |
+ A patch note.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/NoteUpdate'
{% endif %}
Patch:
required: true
@@ -2248,6 +2444,45 @@ components:
$ref: '#/components/schemas/PatchEmbedded'
comment:
$ref: '#/components/schemas/CommentEmbedded'
+{% if version >= (1, 4) %}
+ Note:
+ type: object
+ title: Note
+ description: |
+ A note
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ created_at:
+ title: CreatedAt
+ type: string
+ format: iso8601
+ readOnly: true
+ updated_at:
+ title: UpdatedAt
+ type: string
+ format: iso8601
+ readOnly: true
+ submitter:
+ type: object
+ title: Submitter
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/PersonEmbedded'
+ content:
+ title: Content
+ type: string
+ readOnly: true
+ minLength: 1
+ NoteUpdate:
+ type: object
+ properties:
+ addressed:
+ title: Addressed
+ type: boolean
+{% endif %}
PatchList:
required:
- state
@@ -3251,6 +3486,19 @@ components:
type: array
items:
type: string
+{% endif %}
+{% if version >= (1, 4) %}
+ ErrorNoteUpdate:
+ type: object
+ title: A note update error.
+ description: |
+ A mapping of field names to validation failures.
+ properties:
+ addressed:
+ title: Addressed
+ type: array
+ items:
+ type: string
{% endif %}
ErrorPatchUpdate:
type: object
@@ -3328,6 +3576,10 @@ tags:
description: Series operations
- name: comments
description: Comment operations
+{% if version >= (1, 4) %}
+ - name: notes
+ description: Note operations
+{% endif %}
- name: people
description: Submitter operations
- name: users
diff --git a/docs/api/schemas/v1.4/patchwork.yaml b/docs/api/schemas/v1.4/patchwork.yaml
new file mode 100644
index 000000000..8ac2b8fad
--- /dev/null
+++ b/docs/api/schemas/v1.4/patchwork.yaml
@@ -0,0 +1,3468 @@
+# DO NOT EDIT THIS FILE. It is generated from a template. Changes should be
+# proposed against the template and updated files generated using the
+# 'generate-schemas.py' tool
+---
+openapi: '3.1.0'
+info:
+ title: Patchwork API
+ description: |
+ Patchwork is a web-based patch tracking system designed to facilitate the
+ contribution and management of contributions to an open-source project.
+ contact:
+ email: patchwork@lists.ozlabs.org
+ license:
+ name: GPL v2 License
+ url: https://www.gnu.org/licenses/gpl-2.0.html
+ version: '1.4'
+paths:
+ /api/1.4/:
+ get:
+ summary: List API resources.
+ description: |
+ Show paths to all supported API resources.
+ operationId: api_list
+ parameters: []
+ responses:
+ '200':
+ description: 'List of API resources'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Index'
+ tags:
+ - api
+ /api/1.4/bundles:
+ get:
+ summary: List bundles.
+ description: |
+ List all bundles that the current user has access to.
+ For unauthenticated requests, only public bundles can be shown.
+ operationId: bundles_list
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/PageSize'
+ - $ref: '#/components/parameters/Order'
+ - $ref: '#/components/parameters/Search'
+ - in: query
+ name: project
+ description: An ID or linkname of a project to filter bundles by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: owner
+ description: An ID or username of a user to filter bundles by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: public
+ description: Show only public (`true`) or private (`false`) bundles.
+ schema:
+ title: ''
+ type: string
+ enum:
+ - 'true'
+ - 'false'
+ responses:
+ '200':
+ description: 'List of bundles'
+ headers:
+ Link:
+ $ref: '#/components/headers/Link'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Bundle'
+ tags:
+ - bundles
+ post:
+ summary: Create a bundle.
+ description: |
+ Create a new bundle.
+ operationId: bundles_create
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ requestBody:
+ $ref: '#/components/requestBodies/Bundle'
+ responses:
+ '201':
+ description: 'Created bundle'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Bundle'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorBundleCreateUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - bundles
+ /api/1.4/bundles/{id}:
+ parameters:
+ - in: path
+ name: id
+ required: true
+ description: A unique integer value identifying this bundle.
+ schema:
+ title: ID
+ type: integer
+ get:
+ summary: Show a bundle.
+ description: |
+ Retrieve a bundle by its ID.
+ The bundle must be either be public or be owned by the currently authenticated user.
+ operationId: bundles_read
+ responses:
+ '200':
+ description: 'A bundle'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Bundle'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - bundles
+ patch:
+ summary: Update a bundle (partial).
+ description:
+ Partially update an existing bundle.
+ The bundle must be owned by the currently authenticated user.
+ operationId: bundles_partial_update
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ requestBody:
+ $ref: '#/components/requestBodies/Bundle'
+ responses:
+ '200':
+ description: 'Updated bundle'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Bundle'
+ '400':
+ description: 'Bad request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorBundleCreateUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - bundles
+ put:
+ summary: Update a bundle.
+ description:
+ Update an existing bundle.
+ The bundle must be owned by the currently authenticated user.
+ operationId: bundles_update
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ requestBody:
+ $ref: '#/components/requestBodies/Bundle'
+ responses:
+ '200':
+ description: 'Updated bundle'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Bundle'
+ '400':
+ description: 'Bad request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorBundleCreateUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - bundles
+ /api/1.4/covers:
+ get:
+ summary: List cover letters.
+ description: |
+ List all cover letters.
+ operationId: covers_list
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/PageSize'
+ - $ref: '#/components/parameters/Order'
+ - $ref: '#/components/parameters/Search'
+ - $ref: '#/components/parameters/BeforeFilter'
+ - $ref: '#/components/parameters/SinceFilter'
+ - in: query
+ name: project
+ description: |
+ An ID or linkname of a project to filter cover letters by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: series
+ description: An ID of a series to filter cover letters by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: submitter
+ description: |
+ An ID or email address of a person to filter cover letters by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: msgid
+ description: |
+ The cover message-id as a case-sensitive string, without leading or
+ trailing angle brackets, to filter by.
+ schema:
+ title: ''
+ type: string
+ responses:
+ '200':
+ description: 'List of cover letters'
+ headers:
+ Link:
+ $ref: '#/components/headers/Link'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/CoverList'
+ tags:
+ - covers
+ /api/1.4/covers/{id}:
+ parameters:
+ - in: path
+ name: id
+ description: A unique integer value identifying this cover letter.
+ required: true
+ schema:
+ title: ID
+ type: integer
+ get:
+ summary: Show a cover letter.
+ description: |
+ Retrieve a cover letter by its ID.
+ operationId: covers_read
+ responses:
+ '200':
+ description: 'A cover letter'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CoverDetail'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - covers
+ /api/1.4/covers/{id}/comments:
+ parameters:
+ - in: path
+ name: id
+ description: |
+ A unique integer value identifying the parent cover letter.
+ required: true
+ schema:
+ title: ID
+ type: integer
+ get:
+ summary: List cover letter comments
+ description: |
+ List all comments for the given cover letter.
+ operationId: cover_comments_list
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/PageSize'
+ - $ref: '#/components/parameters/Order'
+ - $ref: '#/components/parameters/Search'
+ responses:
+ '200':
+ description: 'List of comments'
+ headers:
+ Link:
+ $ref: '#/components/headers/Link'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Comment'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - comments
+ /api/1.4/covers/{cover_id}/comments/{comment_id}:
+ parameters:
+ - in: path
+ name: cover_id
+ description: A unique integer value identifying the parent cover.
+ required: true
+ schema:
+ title: Cover ID
+ type: integer
+ - in: path
+ name: comment_id
+ description: A unique integer value identifying this comment.
+ required: true
+ schema:
+ title: Comment ID
+ type: integer
+ get:
+ summary: Show a cover letter comment.
+ description: |
+ Retrieve a cover letter comment by its ID.
+ operationId: cover_comments_read
+ responses:
+ '200':
+ description: 'A cover letter comment'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Comment'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - comments
+ patch:
+ summary: Update a cover letter comment (partial).
+ description:
+ Partially update an existing cover letter comment.
+ You must be a maintainer of the project that the cover letter comment belongs to.
+ operationId: cover_comments_partial_update
+ requestBody:
+ $ref: '#/components/requestBodies/Comment'
+ responses:
+ '200':
+ description: 'Updated cover letter comment'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Comment'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorCommentUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - comments
+ /api/1.4/events:
+ get:
+ summary: List events.
+ description: |
+ List all events.
+ This list can be quite large. You are encouraged to use filters to narrow it to specific categories or project(s).
+ operationId: events_list
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/PageSize'
+ - $ref: '#/components/parameters/Order'
+ - $ref: '#/components/parameters/Search'
+ - $ref: '#/components/parameters/BeforeFilter'
+ - $ref: '#/components/parameters/SinceFilter'
+ - in: query
+ name: project
+ description: An ID or linkname of a project to filter events by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: category
+ description: |
+ An event category to filter events by. These categories are subject
+ to change depending on the version of Patchwork deployed and are
+ not subject to the versionining constraints present across the rest
+ of the API.
+ schema:
+ title: ''
+ type: string
+ enum:
+ - cover-created
+ - patch-created
+ - patch-completed
+ - patch-state-changed
+ - patch-relation-changed
+ - patch-delegated
+ - check-created
+ - series-created
+ - series-completed
+ - cover-comment-created
+ - patch-comment-created
+ - in: query
+ name: series
+ description: An ID of a series to filter events by.
+ schema:
+ title: ''
+ type: integer
+ - in: query
+ name: patch
+ description: An ID of a patch to filter events by.
+ schema:
+ title: ''
+ type: integer
+ - in: query
+ name: cover
+ description: An ID of a cover letter to filter events by.
+ schema:
+ title: ''
+ type: integer
+ responses:
+ '200':
+ description: 'List of events'
+ headers:
+ Link:
+ $ref: '#/components/headers/Link'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ anyOf:
+ - $ref: '#/components/schemas/EventCoverCreated'
+ - $ref: '#/components/schemas/EventPatchCreated'
+ - $ref: '#/components/schemas/EventPatchCompleted'
+ - $ref: '#/components/schemas/EventPatchStateChanged'
+ - $ref: '#/components/schemas/EventPatchRelationChanged'
+ - $ref: '#/components/schemas/EventPatchDelegated'
+ - $ref: '#/components/schemas/EventCheckCreated'
+ - $ref: '#/components/schemas/EventSeriesCreated'
+ - $ref: '#/components/schemas/EventSeriesCompleted'
+ - $ref: '#/components/schemas/EventCoverCommentCreated'
+ - $ref: '#/components/schemas/EventPatchCommentCreated'
+ discriminator:
+ propertyName: category
+ mapping:
+ cover-created: '#/components/schemas/EventCoverCreated'
+ patch-created: '#/components/schemas/EventPatchCreated'
+ patch-completed: '#/components/schemas/EventPatchCompleted'
+ patch-state-changed: '#/components/schemas/EventPatchStateChanged'
+ patch-relation-changed: '#/components/schemas/EventPatchRelationChanged'
+ patch-delegated: '#/components/schemas/EventPatchDelegated'
+ check-created: '#/components/schemas/EventCheckCreated'
+ series-created: '#/components/schemas/EventSeriesCreated'
+ series-completed: '#/components/schemas/EventSeriesCompleted'
+ cover-comment-created: '#/components/schemas/EventCoverCommentCreated'
+ patch-comment-created: '#/components/schemas/EventPatchCommentCreated'
+ tags:
+ - events
+ /api/1.4/patches:
+ get:
+ summary: List patches.
+ description: |
+ List all patches.
+ operationId: patches_list
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/PageSize'
+ - $ref: '#/components/parameters/Order'
+ - $ref: '#/components/parameters/Search'
+ - $ref: '#/components/parameters/BeforeFilter'
+ - $ref: '#/components/parameters/SinceFilter'
+ - in: query
+ name: project
+ description: An ID or linkname of a project to filter patches by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: series
+ description: An ID of a series to filter patches by.
+ schema:
+ title: ''
+ type: integer
+ - in: query
+ name: submitter
+ description: |
+ An ID or email address of a person to filter patches by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: delegate
+ description: An ID or username of a user to filter patches by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: state
+ description: A slug representation of a state to filter patches by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: archived
+ description: |
+ Show only archived (`true`) or non-archived (`false`) patches.
+ schema:
+ title: ''
+ type: string
+ enum:
+ - 'true'
+ - 'false'
+ - in: query
+ name: hash
+ description: |
+ The patch hash as a case-insensitive hexadecimal string, to filter by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: msgid
+ description: |
+ The patch message-id as a case-sensitive string, without leading or
+ trailing angle brackets, to filter by.
+ schema:
+ title: ''
+ type: string
+ responses:
+ '200':
+ description: 'List of patches'
+ headers:
+ Link:
+ $ref: '#/components/headers/Link'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/PatchList'
+ tags:
+ - patches
+ /api/1.4/patches/{id}:
+ parameters:
+ - in: path
+ name: id
+ description: A unique integer value identifying this patch.
+ required: true
+ schema:
+ title: ID
+ type: integer
+ get:
+ summary: Show a patch.
+ description: |
+ Retrieve a patch by its ID.
+ operationId: patches_read
+ responses:
+ '200':
+ description: 'A patch'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PatchDetail'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - patches
+ patch:
+ summary: Update a patch (partial).
+ description:
+ Partially update an existing patch.
+ You must be a maintainer of the project that the patch belongs to.
+ operationId: patches_partial_update
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ requestBody:
+ $ref: '#/components/requestBodies/Patch'
+ responses:
+ '200':
+ description: 'An updated patch'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PatchDetail'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorPatchUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '409':
+ description: 'Conflict'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - patches
+ put:
+ description: Update a patch.
+ operationId: patches_update
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ requestBody:
+ $ref: '#/components/requestBodies/Patch'
+ responses:
+ '200':
+ description: 'An updated patch'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PatchDetail'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorPatchUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '409':
+ description: 'Conflict'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - patches
+ /api/1.4/patches/{id}/comments:
+ parameters:
+ - in: path
+ name: id
+ description: A unique integer value identifying the parent patch.
+ required: true
+ schema:
+ title: ID
+ type: integer
+ get:
+ summary: List patch comments
+ description: |
+ List all comments for the given patch.
+ operationId: patch_comments_list
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/PageSize'
+ - $ref: '#/components/parameters/Order'
+ - $ref: '#/components/parameters/Search'
+ responses:
+ '200':
+ description: 'List of comments'
+ headers:
+ Link:
+ $ref: '#/components/headers/Link'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Comment'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - comments
+ /api/1.4/patches/{patch_id}/comments/{comment_id}:
+ parameters:
+ - in: path
+ name: patch_id
+ description: A unique integer value identifying the parent patch.
+ required: true
+ schema:
+ title: Patch ID
+ type: integer
+ - in: path
+ name: comment_id
+ description: A unique integer value identifying this comment.
+ required: true
+ schema:
+ title: Comment ID
+ type: integer
+ get:
+ summary: Show a patch comment.
+ description: |
+ Retrieve a patch comment by its ID and the ID of the patch.
+ operationId: patch_comments_read
+ responses:
+ '200':
+ description: 'A patch comment'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Comment'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - comments
+ patch:
+ summary: Update a patch comment (partial).
+ description:
+ Partially update an existing patch comment.
+ You must be a maintainer of the project that the patch comment belongs to.
+ operationId: patch_comments_partial_update
+ requestBody:
+ $ref: '#/components/requestBodies/Comment'
+ responses:
+ '200':
+ description: 'Updated patch'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Comment'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorCommentUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - comments
+ /api/1.4/patches/{patch_id}/notes/{note_id}:
+ parameters:
+ - in: path
+ name: patch_id
+ description: A unique integer value identifying the parent patch.
+ required: true
+ schema:
+ title: Patch ID
+ type: integer
+ - in: path
+ name: note_id
+ description: A unique integer value identifying the note.
+ required: true
+ schema:
+ title: Note ID
+ type: integer
+ get:
+ summary: Retrieve a patch note.
+ description: |
+ Retrieve note from a patch by the ID of the patch.
+ operationId: patch_notes_read
+ responses:
+ '200':
+ description: 'A patch note'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ patch:
+ summary: Update a patch note.
+ description:
+ Partially update an existing patch note.
+ You must be a maintainer of the project that the patch note belongs to.
+ operationId: patch_notes_update
+ requestBody:
+ $ref: '#/components/requestBodies/Note'
+ responses:
+ '200':
+ description: 'Updated note'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorNoteUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ delete:
+ summary: Delete a patch note.
+ description:
+ Delete an existing patch note.
+ You must be a maintainer of the project that the patch note belongs to.
+ operationId: patch_notes_delete
+ requestBody:
+ $ref: '#/components/requestBodies/Note'
+ responses:
+ '204':
+ description: 'Delete note'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorNoteUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ /api/1.4/patches/{patch_id}/notes:
+ parameters:
+ - in: path
+ name: patch_id
+ description: A unique integer value identifying the parent patch.
+ required: true
+ schema:
+ title: Patch ID
+ type: integer
+ post:
+ summary: Create a patch note.
+ description: |
+ Create note for a patch.
+ operationId: patch_notes_create
+ responses:
+ '200':
+ description: 'A patch note'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '201':
+ description: 'Created'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Note'
+ '400':
+ description: 'Bad request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+ get:
+ summary: Retrieve all notes from a patch.
+ description: |
+ Retrieve all note from a specific patch.
+ operationId: patch_notes_list
+ responses:
+ '200':
+ description: 'A list of patch notes'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Note'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - notes
+
+ /api/1.4/patches/{patch_id}/checks:
+ parameters:
+ - in: path
+ name: patch_id
+ description: A unique integer value identifying the parent patch.
+ required: true
+ schema:
+ title: Patch ID
+ type: integer
+ get:
+ summary: List checks.
+ description: |
+ List all checks for the given patch.
+ operationId: checks_list
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/PageSize'
+ - $ref: '#/components/parameters/Order'
+ - $ref: '#/components/parameters/Search'
+ - $ref: '#/components/parameters/BeforeFilter'
+ - $ref: '#/components/parameters/SinceFilter'
+ - in: query
+ name: user
+ description: An ID or username of a user to filter checks by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: state
+ description: A check state to filter checks by.
+ schema:
+ title: ''
+ type: string
+ enum:
+ - pending
+ - success
+ - warning
+ - fail
+ - in: query
+ name: context
+ description: A check context to filter checks by.
+ schema:
+ title: ''
+ type: string
+ responses:
+ '200':
+ description: 'List of checks'
+ headers:
+ Link:
+ $ref: '#/components/headers/Link'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Check'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - checks
+ post:
+ summary: Create a check.
+ operationId: checks_create
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ requestBody:
+ $ref: '#/components/requestBodies/Check'
+ responses:
+ '201':
+ description: 'Created check'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Check'
+ '400':
+ description: 'Invalid request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorCheckCreate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - checks
+ /api/1.4/patches/{patch_id}/checks/{check_id}:
+ parameters:
+ - in: path
+ name: patch_id
+ description: A unique integer value identifying the parent patch.
+ required: true
+ schema:
+ title: Patch ID
+ type: integer
+ - in: path
+ name: check_id
+ description: A unique integer value identifying this check.
+ required: true
+ schema:
+ title: Check ID
+ type: integer
+ get:
+ summary: Show a check.
+ description: |
+ Retrieve a check by its ID.
+ operationId: checks_read
+ responses:
+ '200':
+ description: 'A check'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Check'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - checks
+ /api/1.4/people:
+ get:
+ summary: List people.
+ description: |
+ List all people.
+ A person is anyone that has submitted a patch, a series of patches, or a comment to any project.
+ operationId: people_list
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/PageSize'
+ - $ref: '#/components/parameters/Order'
+ - $ref: '#/components/parameters/Search'
+ responses:
+ '200':
+ description: 'List of people'
+ headers:
+ Link:
+ $ref: '#/components/headers/Link'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Person'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - people
+ /api/1.4/people/{id}:
+ parameters:
+ - in: path
+ name: id
+ description: A unique integer value identifying this person.
+ required: true
+ schema:
+ title: ID
+ type: integer
+ get:
+ summary: Show a person.
+ description: |
+ Retrieve a person by their ID.
+ A person is anyone that has submitted a patch, a series of patches, or a comment to any project.
+ operationId: people_read
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ responses:
+ '200':
+ description: 'A person'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Person'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - people
+ /api/1.4/projects:
+ get:
+ summary: List projects.
+ description: |
+ List all projects.
+ operationId: projects_list
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/PageSize'
+ - $ref: '#/components/parameters/Order'
+ - $ref: '#/components/parameters/Search'
+ responses:
+ '200':
+ description: 'List of projects'
+ headers:
+ Link:
+ $ref: '#/components/headers/Link'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Project'
+ tags:
+ - projects
+ /api/1.4/projects/{id}:
+ parameters:
+ - in: path
+ name: id
+ description: A unique integer value identifying this project.
+ required: true
+ schema:
+ title: ID
+ # TODO: Add regex?
+ type: string
+ get:
+ summary: Show a project.
+ description: |
+ Retrieve a project by its ID.
+ operationId: projects_read
+ responses:
+ '200':
+ description: 'A project'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Project'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - projects
+ patch:
+ summary: Update a project (partial).
+ description:
+ Partially update an existing project.
+ You must be a maintainer of the project.
+ operationId: projects_partial_update
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ requestBody:
+ $ref: '#/components/requestBodies/Project'
+ responses:
+ '200':
+ description: 'Updated project'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Project'
+ '400':
+ description: 'Bad request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorProjectUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - projects
+ put:
+ description: Update a project.
+ operationId: projects_update
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ requestBody:
+ $ref: '#/components/requestBodies/Project'
+ responses:
+ '200':
+ description: 'Updated project'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Project'
+ '400':
+ description: 'Bad request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorProjectUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - projects
+ /api/1.4/series:
+ get:
+ summary: List series.
+ description: |
+ List all series.
+ A series is a collection of patches with an optional cover letter.
+ operationId: series_list
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/PageSize'
+ - $ref: '#/components/parameters/Order'
+ - $ref: '#/components/parameters/Search'
+ - $ref: '#/components/parameters/BeforeFilter'
+ - $ref: '#/components/parameters/SinceFilter'
+ - in: query
+ name: submitter
+ description: An ID or email address of a person to filter series by.
+ schema:
+ title: ''
+ type: string
+ - in: query
+ name: project
+ description: An ID or linkname of a project to filter series by.
+ schema:
+ title: ''
+ type: string
+ responses:
+ '200':
+ description: 'List of series'
+ headers:
+ Link:
+ $ref: '#/components/headers/Link'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Series'
+ tags:
+ - series
+ /api/1.4/series/{id}:
+ parameters:
+ - in: path
+ name: id
+ description: A unique integer value identifying this series.
+ required: true
+ schema:
+ title: ID
+ type: integer
+ get:
+ summary: Show a series.
+ description: |
+ Retrieve a series by its ID.
+ A series is a collection of patches with an optional cover letter.
+ operationId: series_read
+ responses:
+ '200':
+ description: 'A series'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Series'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - series
+ /api/1.4/users:
+ get:
+ summary: List users.
+ description: |
+ List all users.
+ operationId: users_list
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ parameters:
+ - $ref: '#/components/parameters/Page'
+ - $ref: '#/components/parameters/PageSize'
+ - $ref: '#/components/parameters/Order'
+ - $ref: '#/components/parameters/Search'
+ responses:
+ '200':
+ description: 'List of users'
+ headers:
+ Link:
+ $ref: '#/components/headers/Link'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/User'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - users
+ /api/1.4/users/{id}:
+ parameters:
+ - in: path
+ name: id
+ description: A unique integer value identifying this user.
+ required: true
+ schema:
+ title: ID
+ type: integer
+ get:
+ summary: Show a user.
+ description: |
+ Retrieve a user by their ID.
+ operationId: users_read
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ responses:
+ '200':
+ description: 'A user'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserDetail'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - users
+ patch:
+ summary: Update a user (partial).
+ description:
+ Partially update a user account.
+ Only super users are allowed to update other user's accounts.
+ operationId: users_partial_update
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ requestBody:
+ $ref: '#/components/requestBodies/User'
+ responses:
+ '200':
+ description: 'Updated user'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserDetail'
+ '400':
+ description: 'Bad request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorUserUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - users
+ put:
+ description: Update a user.
+ operationId: users_update
+ security:
+ - basicAuth: []
+ - apiKeyAuth: []
+ requestBody:
+ $ref: '#/components/requestBodies/User'
+ responses:
+ '200':
+ description: 'Updated user'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserDetail'
+ '400':
+ description: 'Bad request'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorUserUpdate'
+ '403':
+ description: 'Forbidden'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ '404':
+ description: 'Not found'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ tags:
+ - users
+components:
+ securitySchemes:
+ basicAuth:
+ type: http
+ scheme: basic
+ description: |
+ Basic authentication. This should be avoided and may be removed in a future API release.
+ apiKeyAuth:
+ type: http
+ scheme: token
+ description: |
+ Token-based authentication.
+ cookieAuth:
+ type: apiKey
+ in: cookie
+ name: JSESSIONID
+ description: |
+ Cookie-based authentication. This is mainly used for the browsable API.
+ parameters:
+ Page:
+ in: query
+ name: page
+ description: A page number within the paginated result set.
+ schema:
+ title: Page
+ type: integer
+ PageSize:
+ in: query
+ name: per_page
+ description: Number of results to return per page.
+ schema:
+ title: Page size
+ type: integer
+ Order:
+ in: query
+ name: order
+ description: Which field to use when ordering the results.
+ schema:
+ title: Ordering
+ type: string
+ Search:
+ in: query
+ name: q
+ description: A search term.
+ schema:
+ title: Search
+ type: string
+ BeforeFilter:
+ in: query
+ name: before
+ description: Latest date-time to retrieve results for.
+ schema:
+ title: ''
+ type: string
+ SinceFilter:
+ in: query
+ name: since
+ description: Earliest date-time to retrieve results for.
+ schema:
+ title: ''
+ type: string
+ headers:
+ Link:
+ description: |
+ Links to related resources, in the format defined by
+ [RFC 5988](https://tools.ietf.org/html/rfc5988#section-5).
+ This will include a link with relation type `next` to the
+ next page and `prev` to the previous page, if there is a next
+ or previous page. It will also include links with the
+ relation type `first` and `last` pointing to the first and
+ last page, respectively.
+ schema:
+ type: string
+ requestBodies:
+ Bundle:
+ required: true
+ description: |
+ A patch bundle.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/BundleCreateUpdate'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/BundleCreateUpdate'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/BundleCreateUpdate'
+ Check:
+ required: true
+ description: |
+ A patch check.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CheckCreate'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/CheckCreate'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/CheckCreate'
+ Comment:
+ required: true
+ description: |
+ A patch or cover letter comment.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CommentUpdate'
+ Note:
+ required: true
+ description: |
+ A patch note.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/NoteUpdate'
+ Patch:
+ required: true
+ description: |
+ A patch.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PatchUpdate'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/PatchUpdate'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/PatchUpdate'
+ Project:
+ required: true
+ description: |
+ A project.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Project'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/Project'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/Project'
+ User:
+ required: true
+ description: |
+ A user.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UserDetail'
+ multipart/form-data:
+ schema:
+ $ref: '#/components/schemas/UserDetail'
+ application/x-www-form-urlencoded:
+ schema:
+ $ref: '#/components/schemas/UserDetail'
+ schemas:
+ Index:
+ type: object
+ name: Index
+ description: |
+ Paths to resource APIs
+ properties:
+ bundles:
+ title: Bundles URL
+ type: string
+ format: uri
+ readOnly: true
+ covers:
+ title: Covers URL
+ type: string
+ format: uri
+ readOnly: true
+ events:
+ title: Events URL
+ type: string
+ format: uri
+ readOnly: true
+ patches:
+ title: Patches URL
+ type: string
+ format: uri
+ readOnly: true
+ people:
+ title: People URL
+ type: string
+ format: uri
+ readOnly: true
+ projects:
+ title: Projects URL
+ type: string
+ format: uri
+ readOnly: true
+ users:
+ title: Users URL
+ type: string
+ format: uri
+ readOnly: true
+ series:
+ title: Series URL
+ type: string
+ format: uri
+ readOnly: true
+ Bundle:
+ required:
+ - name
+ type: object
+ title: Bundle
+ description: |
+ A patch bundle
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ readOnly: true
+ project:
+ $ref: '#/components/schemas/ProjectEmbedded'
+ name:
+ title: Name
+ type: string
+ minLength: 1
+ maxLength: 50
+ owner:
+ title: Owner
+ readOnly: true
+ type:
+ - 'null'
+ - 'object'
+ oneOf:
+ - type: 'null'
+ - $ref: '#/components/schemas/UserEmbedded'
+ patches:
+ title: Patches
+ type: array
+ items:
+ $ref: '#/components/schemas/PatchEmbedded'
+ uniqueItems: true
+ public:
+ title: Public
+ type: boolean
+ mbox:
+ title: Mbox
+ description: |
+ A URL to download the bundle in mbox format. Patches will be
+ ordered in the same order that they are defined in the bundle.
+ type: string
+ format: uri
+ readOnly: true
+ BundleCreateUpdate:
+ type: object
+ title: Bundle create or update
+ description: |
+ The fields to set on a new or existing bundle.
+ required:
+ - name
+ properties:
+ name:
+ title: Name
+ type: string
+ minLength: 1
+ maxLength: 50
+ patches:
+ title: Patches
+ type: array
+ items:
+ type: integer
+ uniqueItems: true
+ public:
+ title: Public
+ type: boolean
+ Check:
+ type: object
+ title: Check
+ description: |
+ A patch check
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: Url
+ type: string
+ format: uri
+ readOnly: true
+ user:
+ $ref: '#/components/schemas/UserEmbedded'
+ date:
+ title: Date
+ type: string
+ format: iso8601
+ readOnly: true
+ state:
+ title: State
+ description: The state of the check.
+ type: string
+ enum:
+ - pending
+ - success
+ - warning
+ - fail
+ target_url:
+ title: Target URL
+ description: |
+ The target URL to associate with this check. This should be
+ specific to the patch.
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 200
+ context:
+ title: Context
+ description: |
+ A label to discern check from checks of other testing systems.
+ type: string
+ pattern: ^[-a-zA-Z0-9_]+$
+ minLength: 1
+ maxLength: 255
+ description:
+ title: Description
+ description: A brief description of the check.
+ type:
+ - 'null'
+ - 'string'
+ CheckCreate:
+ type: object
+ title: Check
+ description: |
+ A patch check
+ required:
+ - state
+ properties:
+ state:
+ title: State
+ description: The state of the check.
+ type: string
+ enum:
+ - pending
+ - success
+ - warning
+ - fail
+ target_url:
+ title: Target URL
+ description: |
+ The target URL to associate with this check. This should be
+ specific to the patch.
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 200
+ context:
+ title: Context
+ description: |
+ A label to discern check from checks of other testing systems.
+ type: string
+ pattern: ^[-a-zA-Z0-9_]+$
+ minLength: 1
+ maxLength: 255
+ description:
+ title: Description
+ description: A brief description of the check.
+ type:
+ - 'null'
+ - 'string'
+ Comment:
+ type: object
+ title: Comment
+ description: |
+ A comment
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ readOnly: true
+ msgid:
+ title: Message ID
+ type: string
+ readOnly: true
+ minLength: 1
+ maxLength: 255
+ list_archive_url:
+ title: List archive URL
+ readOnly: true
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 2000
+ date:
+ title: Date
+ type: string
+ format: iso8601
+ readOnly: true
+ subject:
+ title: Subject
+ type: string
+ readOnly: true
+ submitter:
+ type: object
+ title: Submitter
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/PersonEmbedded'
+ content:
+ title: Content
+ type: string
+ readOnly: true
+ minLength: 1
+ headers:
+ title: Headers
+ anyOf:
+ - type: object
+ additionalProperties:
+ type: array
+ items:
+ type: string
+ - type: object
+ additionalProperties:
+ type: string
+ readOnly: true
+ addressed:
+ title: Addressed
+ type:
+ - 'null'
+ - 'boolean'
+ CommentUpdate:
+ type: object
+ title: Comment update
+ description: |
+ The fields to set on an existing comment.
+ properties:
+ addressed:
+ title: Addressed
+ type:
+ - 'null'
+ - 'boolean'
+ CoverList:
+ type: object
+ title: Cover letters
+ description: |
+ A list of cover letters
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ readOnly: true
+ project:
+ $ref: '#/components/schemas/ProjectEmbedded'
+ msgid:
+ title: Message ID
+ type: string
+ readOnly: true
+ minLength: 1
+ maxLength: 255
+ list_archive_url:
+ title: List archive URL
+ readOnly: true
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 2000
+ date:
+ title: Date
+ type: string
+ format: iso8601
+ readOnly: true
+ name:
+ title: Name
+ type: string
+ readOnly: true
+ minLength: 1
+ maxLength: 255
+ submitter:
+ type: object
+ title: Submitter
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/PersonEmbedded'
+ mbox:
+ title: Mbox
+ description: |
+ A URL to download the cover letter in mbox format.
+ type: string
+ format: uri
+ readOnly: true
+ series:
+ type: array
+ items:
+ $ref: '#/components/schemas/SeriesEmbedded'
+ readOnly: true
+ comments:
+ title: Comments
+ type: string
+ format: uri
+ readOnly: true
+ CoverDetail:
+ type: object
+ title: Cover letters
+ description: |
+ A list of cover letters
+ allOf:
+ - $ref: '#/components/schemas/CoverList'
+ - type: object
+ properties:
+ headers:
+ title: Headers
+ anyOf:
+ - type: object
+ additionalProperties:
+ type: array
+ items:
+ type: string
+ - type: object
+ additionalProperties:
+ type: string
+ readOnly: true
+ content:
+ title: Content
+ type: string
+ readOnly: true
+ minLength: 1
+ EventBase:
+ type: object
+ title: Event base
+ description: |
+ Base event. Not directly used.
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ category:
+ title: Category
+ description: The category of the event.
+ type: string
+ readOnly: true
+ project:
+ $ref: '#/components/schemas/ProjectEmbedded'
+ date:
+ title: Date
+ description: The time this event was created.
+ type: string
+ format: iso8601
+ readOnly: true
+ actor:
+ title: Actor
+ description: The user that caused/created this event.
+ readOnly: true
+ type:
+ - 'null'
+ - 'object'
+ oneOf:
+ - type: 'null'
+ - $ref: '#/components/schemas/UserEmbedded'
+ payload:
+ type: object
+ EventCoverCreated:
+ title: Cover created event
+ description: |
+ A cover created event.
+ allOf:
+ - $ref: '#/components/schemas/EventBase'
+ - type: object
+ properties:
+ category:
+ enum:
+ - cover-created
+ payload:
+ properties:
+ cover:
+ $ref: '#/components/schemas/CoverEmbedded'
+ EventPatchCreated:
+ title: Patch created event
+ description: |
+ A patch created event.
+ allOf:
+ - $ref: '#/components/schemas/EventBase'
+ - type: object
+ properties:
+ category:
+ enum:
+ - patch-created
+ payload:
+ properties:
+ patch:
+ $ref: '#/components/schemas/PatchEmbedded'
+ EventPatchCompleted:
+ title: Patch completed event
+ description: |
+ A patch completed event.
+ allOf:
+ - $ref: '#/components/schemas/EventBase'
+ - type: object
+ properties:
+ category:
+ enum:
+ - patch-completed
+ payload:
+ properties:
+ patch:
+ $ref: '#/components/schemas/PatchEmbedded'
+ series:
+ $ref: '#/components/schemas/SeriesEmbedded'
+ EventPatchStateChanged:
+ title: Patch state change event
+ description: |
+ A patch state changed event.
+ allOf:
+ - $ref: '#/components/schemas/EventBase'
+ - type: object
+ properties:
+ category:
+ enum:
+ - patch-state-changed
+ payload:
+ properties:
+ patch:
+ $ref: '#/components/schemas/PatchEmbedded'
+ previous_state:
+ title: Previous state
+ type: string
+ current_state:
+ title: Current state
+ type: string
+ EventPatchRelationChanged:
+ title: Patch relation change event
+ description: |
+ A patch relation changed event.
+ allOf:
+ - $ref: '#/components/schemas/EventBase'
+ - type: object
+ properties:
+ category:
+ enum:
+ - patch-relation-changed
+ payload:
+ properties:
+ patch:
+ $ref: '#/components/schemas/PatchEmbedded'
+ previous_relation:
+ title: Previous relation
+ type:
+ - 'null'
+ - 'string'
+ current_relation:
+ title: Current relation
+ type:
+ - 'null'
+ - 'string'
+ EventPatchDelegated:
+ title: Patch delegated event
+ description: |
+ A patch delegated event.
+ allOf:
+ - $ref: '#/components/schemas/EventBase'
+ - type: object
+ properties:
+ category:
+ enum:
+ - patch-delegated
+ payload:
+ properties:
+ patch:
+ $ref: '#/components/schemas/PatchEmbedded'
+ previous_delegate:
+ title: Previous delegate
+ type:
+ - 'null'
+ - 'object'
+ oneOf:
+ - type: 'null'
+ - $ref: '#/components/schemas/UserEmbedded'
+ current_delegate:
+ title: Current delegate
+ type:
+ - 'null'
+ - 'object'
+ oneOf:
+ - type: 'null'
+ - $ref: '#/components/schemas/UserEmbedded'
+ EventCheckCreated:
+ title: Check create event
+ description: |
+ A check created event.
+ allOf:
+ - $ref: '#/components/schemas/EventBase'
+ - type: object
+ properties:
+ category:
+ enum:
+ - check-created
+ payload:
+ properties:
+ patch:
+ $ref: '#/components/schemas/PatchEmbedded'
+ check:
+ $ref: '#/components/schemas/CheckEmbedded'
+ EventSeriesCreated:
+ title: Series create event
+ description: |
+ A series created event.
+ allOf:
+ - $ref: '#/components/schemas/EventBase'
+ - type: object
+ properties:
+ category:
+ enum:
+ - series-created
+ payload:
+ properties:
+ series:
+ $ref: '#/components/schemas/SeriesEmbedded'
+ EventSeriesCompleted:
+ title: Series completed event
+ description: |
+ A series completed event.
+ allOf:
+ - $ref: '#/components/schemas/EventBase'
+ - type: object
+ properties:
+ category:
+ enum:
+ - series-completed
+ payload:
+ properties:
+ series:
+ $ref: '#/components/schemas/SeriesEmbedded'
+ EventCoverCommentCreated:
+ title: Cover letter comment create event
+ description: |
+ A comment letter comment created event.
+ allOf:
+ - $ref: '#/components/schemas/EventBase'
+ - type: object
+ properties:
+ category:
+ enum:
+ - cover-comment-created
+ payload:
+ properties:
+ cover:
+ $ref: '#/components/schemas/CoverEmbedded'
+ comment:
+ $ref: '#/components/schemas/CommentEmbedded'
+ EventPatchCommentCreated:
+ title: Patch comment create event
+ description: |
+ A patch comment created event.
+ allOf:
+ - $ref: '#/components/schemas/EventBase'
+ - type: object
+ properties:
+ category:
+ enum:
+ - patch-comment-created
+ payload:
+ properties:
+ patch:
+ $ref: '#/components/schemas/PatchEmbedded'
+ comment:
+ $ref: '#/components/schemas/CommentEmbedded'
+ Note:
+ type: object
+ title: Note
+ description: |
+ A note
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ created_at:
+ title: CreatedAt
+ type: string
+ format: iso8601
+ readOnly: true
+ updated_at:
+ title: UpdatedAt
+ type: string
+ format: iso8601
+ readOnly: true
+ submitter:
+ type: object
+ title: Submitter
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/PersonEmbedded'
+ content:
+ title: Content
+ type: string
+ readOnly: true
+ minLength: 1
+ NoteUpdate:
+ type: object
+ properties:
+ addressed:
+ title: Addressed
+ type: boolean
+ PatchList:
+ required:
+ - state
+ - delegate
+ type: object
+ title: Patches
+ description: |
+ A list of patches.
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ readOnly: true
+ project:
+ $ref: '#/components/schemas/ProjectEmbedded'
+ msgid:
+ title: Message ID
+ type: string
+ readOnly: true
+ minLength: 1
+ maxLength: 255
+ list_archive_url:
+ title: List archive URL
+ readOnly: true
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 2000
+ date:
+ title: Date
+ type: string
+ format: iso8601
+ readOnly: true
+ name:
+ title: Name
+ type: string
+ readOnly: true
+ minLength: 1
+ maxLength: 255
+ commit_ref:
+ title: Commit ref
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ maxLength: 255
+ pull_url:
+ title: Pull URL
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 255
+ state:
+ title: State
+ type: string
+ archived:
+ title: Archived
+ type: boolean
+ hash:
+ title: Hash
+ type: string
+ readOnly: true
+ minLength: 1
+ submitter:
+ type: object
+ title: Submitter
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/PersonEmbedded'
+ delegate:
+ title: Delegate
+ readOnly: true
+ type:
+ - 'null'
+ - 'object'
+ oneOf:
+ - type: 'null'
+ - $ref: '#/components/schemas/UserEmbedded'
+ mbox:
+ title: Mbox
+ description: |
+ A URL to download the patch in mbox format. Add the `series=*`
+ querystring parameter to include series dependencies in the mbox
+ file.
+ type: string
+ format: uri
+ readOnly: true
+ series:
+ type: array
+ items:
+ $ref: '#/components/schemas/SeriesEmbedded'
+ readOnly: true
+ comments:
+ title: Comments
+ type: string
+ format: uri
+ readOnly: true
+ check:
+ title: Check
+ type: string
+ readOnly: true
+ enum:
+ - pending
+ - success
+ - warning
+ - fail
+ checks:
+ title: Checks
+ type: string
+ format: uri
+ readOnly: true
+ tags:
+ title: Tags
+ type: object
+ additionalProperties:
+ type: string
+ readOnly: true
+ related:
+ title: Relations
+ type: array
+ items:
+ $ref: '#/components/schemas/PatchEmbedded'
+ PatchDetail:
+ type: object
+ title: Patches
+ description: |
+ A list of patches.
+ allOf:
+ - $ref: '#/components/schemas/PatchList'
+ - type: object
+ properties:
+ headers:
+ title: Headers
+ anyOf:
+ - type: object
+ additionalProperties:
+ type: array
+ items:
+ type: string
+ - type: object
+ additionalProperties:
+ type: string
+ readOnly: true
+ content:
+ title: Content
+ type: string
+ readOnly: true
+ minLength: 1
+ diff:
+ title: Diff
+ type: string
+ readOnly: true
+ minLength: 1
+ prefixes:
+ title: Prefixes
+ type: array
+ items:
+ type: string
+ readOnly: true
+ PatchUpdate:
+ type: object
+ title: Patch update
+ description: |
+ The fields to set on an existing patch.
+ properties:
+ commit_ref:
+ title: Commit ref
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ maxLength: 255
+ pull_url:
+ title: Pull URL
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 255
+ state:
+ title: State
+ type: string
+ archived:
+ title: Archived
+ type: boolean
+ delegate:
+ title: Delegate
+ type:
+ - 'null'
+ - 'integer'
+ related:
+ title: Relations
+ type: array
+ items:
+ type: integer
+ Person:
+ type: object
+ title: Person
+ description: |
+ A person
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ name:
+ title: Name
+ type: string
+ readOnly: true
+ minLength: 1
+ maxLength: 255
+ email:
+ title: Email
+ type: string
+ format: email
+ readOnly: true
+ minLength: 1
+ maxLength: 255
+ user:
+ title: User
+ readOnly: true
+ type:
+ - 'null'
+ - 'object'
+ oneOf:
+ - type: 'null'
+ - $ref: '#/components/schemas/UserEmbedded'
+ Project:
+ type: object
+ title: Project
+ description: |
+ A project.
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ name:
+ title: Name
+ type: string
+ readOnly: true
+ minLength: 1
+ maxLength: 255
+ link_name:
+ title: Link name
+ type: string
+ readOnly: true
+ minLength: 1
+ maxLength: 255
+ list_id:
+ title: List ID
+ type: string
+ readOnly: true
+ minLength: 1
+ maxLength: 255
+ list_email:
+ title: List email
+ type: string
+ format: email
+ readOnly: true
+ minLength: 1
+ maxLength: 200
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ maxLength: 2000
+ scm_url:
+ title: SCM URL
+ type: string
+ format: uri
+ maxLength: 2000
+ webscm_url:
+ title: Web SCM URL
+ type: string
+ format: uri
+ maxLength: 2000
+ maintainers:
+ type: array
+ items:
+ $ref: '#/components/schemas/UserEmbedded'
+ readOnly: true
+ uniqueItems: true
+ subject_match:
+ title: Subject match
+ description: |
+ Regex to match the subject against if only part of emails sent to
+ the list belongs to this project. Will be used with IGNORECASE and
+ MULTILINE flags. If rules for more projects match the first one
+ returned from DB is chosen; empty field serves as a default for
+ every email which has no other match.
+ type: string
+ readOnly: true
+ maxLength: 64
+ list_archive_url:
+ title: List archive URL
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 2000
+ list_archive_url_format:
+ title: List archive URL format
+ description: |
+ URL format for the list archive's Message-ID redirector. {} will be
+ replaced by the Message-ID.
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 2000
+ commit_url_format:
+ title: Web SCM URL format for a particular commit
+ type: string
+ Series:
+ type: object
+ title: Series
+ description: |
+ A series
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ readOnly: true
+ project:
+ $ref: '#/components/schemas/ProjectEmbedded'
+ name:
+ title: Name
+ description: |
+ An optional name to associate with the series, e.g. "John's PCI
+ series".
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: 'string'
+ maxLength: 255
+ date:
+ title: Date
+ type: string
+ format: iso8601
+ readOnly: true
+ submitter:
+ type: object
+ title: Submitter
+ readOnly: true
+ allOf:
+ - $ref: '#/components/schemas/PersonEmbedded'
+ version:
+ title: Version
+ description: |
+ Version of series as indicated by the subject prefix(es).
+ type: integer
+ total:
+ title: Total
+ description: |
+ Number of patches in series as indicated by the subject prefix(es).
+ type: integer
+ readOnly: true
+ received_total:
+ title: Received total
+ type: integer
+ readOnly: true
+ received_all:
+ title: Received all
+ type: boolean
+ readOnly: true
+ mbox:
+ title: Mbox
+ description: |
+ A URL to download the series in mbox format.
+ type: string
+ format: uri
+ readOnly: true
+ cover_letter:
+ $ref: '#/components/schemas/CoverEmbedded'
+ patches:
+ title: Patches
+ type: array
+ items:
+ $ref: '#/components/schemas/PatchEmbedded'
+ readOnly: true
+ uniqueItems: true
+ User:
+ type: object
+ title: User
+ description: |
+ A user
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ username:
+ title: Username
+ type: string
+ readOnly: true
+ minLength: 1
+ maxLength: 150
+ first_name:
+ title: First name
+ type: string
+ maxLength: 30
+ last_name:
+ title: Last name
+ type: string
+ maxLength: 150
+ email:
+ title: Email address
+ type: string
+ format: email
+ readOnly: true
+ minLength: 1
+ UserDetail:
+ type: object
+ title: User
+ description: |
+ A user
+ allOf:
+ - $ref: '#/components/schemas/User'
+ - type: object
+ properties:
+ settings:
+ type: object
+ properties:
+ send_email:
+ title: Send email
+ description: |
+ Whether Patchwork should send email on your behalf.
+ Only present and configurable for your account.
+ type: boolean
+ items_per_page:
+ title: Items per page
+ description: |
+ Number of items to display per page (web UI).
+ Only present and configurable for your account.
+ type: integer
+ show_ids:
+ title: Show IDs
+ description: |
+ Show click-to-copy IDs in the list view (web UI).
+ Only present and configurable for your account.
+ type: boolean
+ CheckEmbedded:
+ type: object
+ title: Check
+ description: |
+ A patch check
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: Url
+ type: string
+ format: uri
+ readOnly: true
+ date:
+ title: Date
+ type: string
+ format: iso8601
+ readOnly: true
+ state:
+ title: State
+ description: The state of the check.
+ type: string
+ readOnly: true
+ enum:
+ - pending
+ - success
+ - warning
+ - fail
+ target_url:
+ title: Target url
+ description: |
+ The target URL to associate with this check. This should be specific
+ to the patch.
+ readOnly: true
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 200
+ context:
+ title: Context
+ description: |
+ A label to discern check from checks of other testing systems.
+ type: string
+ pattern: ^[-a-zA-Z0-9_]+$
+ maxLength: 255
+ minLength: 1
+ readOnly: true
+ CommentEmbedded:
+ type: object
+ title: Comment
+ description: |
+ A comment
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ readOnly: true
+ msgid:
+ title: Message ID
+ type: string
+ readOnly: true
+ minLength: 1
+ list_archive_url:
+ title: List archive URL
+ readOnly: true
+ type:
+ - 'null'
+ - 'string'
+ date:
+ title: Date
+ type: string
+ format: iso8601
+ readOnly: true
+ name:
+ title: Name
+ type: string
+ readOnly: true
+ minLength: 1
+ CoverEmbedded:
+ type: object
+ title: Cover letter
+ description: |
+ A cover letter
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ readOnly: true
+ msgid:
+ title: Message ID
+ type: string
+ readOnly: true
+ minLength: 1
+ list_archive_url:
+ title: List archive URL
+ readOnly: true
+ type:
+ - 'null'
+ - 'string'
+ date:
+ title: Date
+ type: string
+ format: iso8601
+ readOnly: true
+ name:
+ title: Name
+ type: string
+ readOnly: true
+ minLength: 1
+ mbox:
+ title: Mbox
+ description: |
+ A URL to download the cover letter in mbox format.
+ type: string
+ format: uri
+ readOnly: true
+ PatchEmbedded:
+ type: object
+ title: Patch
+ description: |
+ A patch
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ readOnly: true
+ msgid:
+ title: Message ID
+ type: string
+ readOnly: true
+ minLength: 1
+ list_archive_url:
+ title: List archive URL
+ readOnly: true
+ type:
+ - 'null'
+ - 'string'
+ date:
+ title: Date
+ type: string
+ format: iso8601
+ readOnly: true
+ name:
+ title: Name
+ type: string
+ readOnly: true
+ minLength: 1
+ mbox:
+ title: Mbox
+ description: |
+ A URL to download the patch in mbox format. Add the `series=*`
+ querystring parameter to include series dependencies in the mbox
+ file.
+ type: string
+ format: uri
+ readOnly: true
+ PersonEmbedded:
+ type: object
+ title: Person
+ description: |
+ A person
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ name:
+ title: Name
+ type: string
+ readOnly: true
+ minLength: 1
+ email:
+ title: Email
+ type: string
+ format: email
+ readOnly: true
+ minLength: 1
+ ProjectEmbedded:
+ type: object
+ title: Project
+ description: |
+ A project
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ name:
+ title: Name
+ type: string
+ readOnly: true
+ minLength: 1
+ link_name:
+ title: Link name
+ type: string
+ readOnly: true
+ maxLength: 255
+ minLength: 1
+ list_id:
+ title: List ID
+ type: string
+ readOnly: true
+ maxLength: 255
+ minLength: 1
+ list_email:
+ title: List email
+ type: string
+ format: email
+ readOnly: true
+ maxLength: 200
+ minLength: 1
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ readOnly: true
+ maxLength: 2000
+ scm_url:
+ title: SCM URL
+ type: string
+ format: uri
+ readOnly: true
+ maxLength: 2000
+ webscm_url:
+ title: WebSCM URL
+ type: string
+ format: uri
+ readOnly: true
+ maxLength: 2000
+ list_archive_url:
+ title: List archive URL
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 2000
+ list_archive_url_format:
+ title: List archive URL format
+ description: |
+ URL format for the list archive's Message-ID redirector. {} will be
+ replaced by the Message-ID.
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ format: uri
+ maxLength: 2000
+ commit_url_format:
+ title: Web SCM URL format for a particular commit
+ type: string
+ readOnly: true
+ SeriesEmbedded:
+ type: object
+ title: Series
+ description: |
+ A series
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ readOnly: true
+ name:
+ title: Name
+ description: |
+ An optional name to associate with the series, e.g. "John's PCI
+ series".
+ readOnly: true
+ type:
+ - 'null'
+ - 'string'
+ oneOf:
+ - type: 'null'
+ - type: string
+ maxLength: 255
+ date:
+ title: Date
+ type: string
+ format: iso8601
+ readOnly: true
+ version:
+ title: Version
+ description: |
+ Version of series as indicated by the subject prefix(es).
+ type: integer
+ readOnly: true
+ mbox:
+ title: Mbox
+ description: |
+ A URL to download the series in mbox format.
+ type: string
+ format: uri
+ readOnly: true
+ UserEmbedded:
+ type: object
+ title: User
+ description: |
+ A user
+ properties:
+ id:
+ title: ID
+ type: integer
+ readOnly: true
+ url:
+ title: URL
+ type: string
+ format: uri
+ readOnly: true
+ username:
+ title: Username
+ type: string
+ readOnly: true
+ minLength: 1
+ maxLength: 150
+ first_name:
+ title: First name
+ type: string
+ maxLength: 30
+ readOnly: true
+ last_name:
+ title: Last name
+ type: string
+ maxLength: 150
+ readOnly: true
+ email:
+ title: Email address
+ type: string
+ format: email
+ readOnly: true
+ minLength: 1
+ Error:
+ type: object
+ title: A generic error.
+ description: |
+ A generic error.
+ properties:
+ detail:
+ title: Detail
+ type: string
+ readOnly: true
+ ErrorBundleCreateUpdate:
+ type: object
+ title: A bundle creation or update error.
+ description: |
+ A mapping of field names to validation failures.
+ properties:
+ name:
+ title: Name
+ type: array
+ items:
+ type: string
+ readOnly: true
+ patches:
+ title: Patches
+ type: array
+ items:
+ type: string
+ readOnly: true
+ public:
+ title: Public
+ type: array
+ items:
+ type: string
+ ErrorCheckCreate:
+ type: object
+ title: A check creation error.
+ description: |
+ A mapping of field names to validation failures.
+ properties:
+ state:
+ title: State
+ type: array
+ items:
+ type: string
+ readOnly: true
+ target_url:
+ title: Target URL
+ type: array
+ items:
+ type: string
+ readOnly: true
+ context:
+ title: Context
+ type: array
+ items:
+ type: string
+ readOnly: true
+ description:
+ title: Description
+ type: array
+ items:
+ type: string
+ readOnly: true
+ ErrorCommentUpdate:
+ type: object
+ title: A comment update error.
+ description: |
+ A mapping of field names to validation failures.
+ properties:
+ addressed:
+ title: Addressed
+ type: array
+ items:
+ type: string
+ ErrorNoteUpdate:
+ type: object
+ title: A note update error.
+ description: |
+ A mapping of field names to validation failures.
+ properties:
+ addressed:
+ title: Addressed
+ type: array
+ items:
+ type: string
+ ErrorPatchUpdate:
+ type: object
+ title: A patch update error.
+ description: |
+ A mapping of field names to validation failures.
+ properties:
+ state:
+ title: State
+ type: array
+ items:
+ type: string
+ readOnly: true
+ delegate:
+ title: Delegate
+ type: array
+ items:
+ type: string
+ readOnly: true
+ commit_ref:
+ title: Commit ref
+ type: array
+ items:
+ type: string
+ readOnly: true
+ archived:
+ title: Archived
+ type: array
+ items:
+ type: string
+ readOnly: true
+ ErrorProjectUpdate:
+ type: object
+ title: A project update error.
+ description: |
+ A mapping of field names to validation failures.
+ properties:
+ web_url:
+ title: Web URL
+ type: string
+ format: uri
+ readOnly: true
+ scm_url:
+ title: SCM URL
+ type: string
+ format: uri
+ readOnly: true
+ webscm_url:
+ title: Web SCM URL
+ type: string
+ format: uri
+ readOnly: true
+ ErrorUserUpdate:
+ type: object
+ title: A user update error.
+ description: |
+ A mapping of field names to validation failures.
+ properties:
+ first_name:
+ title: First name
+ type: string
+ readOnly: true
+ last_name:
+ title: First name
+ type: string
+ readOnly: true
+tags:
+ - name: api
+ description: General API operations
+ - name: patches
+ description: Patch operations
+ - name: covers
+ description: Cover letter operations
+ - name: series
+ description: Series operations
+ - name: comments
+ description: Comment operations
+ - name: notes
+ description: Note operations
+ - name: people
+ description: Submitter operations
+ - name: users
+ description: User operations
+ - name: bundles
+ description: Bundle operations
+ - name: projects
+ description: Project operations
+ - name: bundles
+ description: Bundle operations
+ - name: checks
+ description: Check operations
+ - name: events
+ description: Event operations
diff --git a/docs/development/installation.rst b/docs/development/installation.rst
index 6f4920a62..c33f768b1 100644
--- a/docs/development/installation.rst
+++ b/docs/development/installation.rst
@@ -30,14 +30,20 @@ configure Patchwork using Docker:
package.
__ post-install_
-#. Create a ``.env`` file in the root directory of the project and store your
- ``UID`` and ``GID`` attribute there.
+#. (Optional) Create a ``.env`` file in the root directory of the project and
+ store your ``UID`` and ``GID`` attribute there.
.. code-block:: shell
$ echo "UID=$UID" > .env
$ echo "GID=`id -g`" >> .env
+ This should only be necessary if you have a ``UID`` or ``GID`` other than
+ ``1000``. For more information on why this is necessary, refer to this
+ `docker-compose issue`__.
+
+ __ https://github.com/docker/compose/issues/2380
+
#. Build the images. This will download over 200MB from the internet:
.. code-block:: shell
@@ -140,18 +146,6 @@ For more information on Docker itself, please refer to the `docker`_ and
__ post-install_
-.. note::
-
- If you see an error like the below::
-
- You must define UID in .env
-
- Ensure you have created a ``.env`` file in the root of your project
- directory and stored the ``UID`` attribute there. For more information on
- why this is necessary, refer to this `docker-compose issue`__.
-
- __ https://github.com/docker/compose/issues/2380
-
.. _docker: https://docs.docker.com/engine/install/
.. _docker-compose: https://docs.docker.com/compose/install/
.. _post-install: https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user
diff --git a/docs/index.rst b/docs/index.rst
index b58cdc300..33b4981fa 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -58,6 +58,7 @@ of community projects.
:caption: Release Notes
releases/unreleased
+ releases/iridescent
releases/hessian
releases/grosgrain
releases/flannel
diff --git a/docs/releases/index.rst b/docs/releases/index.rst
index 327513266..8c862a383 100644
--- a/docs/releases/index.rst
+++ b/docs/releases/index.rst
@@ -10,6 +10,7 @@ on the release process, refer to :doc:`/development/releasing`.
:maxdepth: 2
/releases/unreleased
+ /releases/iridescent
/releases/hessian
/releases/grosgrain
/releases/flannel
diff --git a/docs/releases/iridescent.rst b/docs/releases/iridescent.rst
new file mode 100644
index 000000000..2954c4610
--- /dev/null
+++ b/docs/releases/iridescent.rst
@@ -0,0 +1,5 @@
+v3.2 Series ("Iridescent")
+==========================
+
+.. release-notes::
+ :branch: stable/3.2
diff --git a/docs/releases/unreleased.rst b/docs/releases/unreleased.rst
index aae1aa96c..80ff4514c 100644
--- a/docs/releases/unreleased.rst
+++ b/docs/releases/unreleased.rst
@@ -2,4 +2,4 @@ Unreleased
==========
.. release-notes::
- :earliest-version: v3.1.0
+ :earliest-version: v3.2.0
diff --git a/htdocs/README.rst b/htdocs/README.rst
index 22fe58d1f..9a5211233 100644
--- a/htdocs/README.rst
+++ b/htdocs/README.rst
@@ -122,6 +122,12 @@ js
:GitHub: https://github.com/js-cookie/js-cookie/
:Version: 3.0.0
+``patch-list.js.``
+ Event helpers and other application logic for patch-list.html. These
+ support patch list manipulation.
+
+ Part of Patchwork.
+
``rest.js.``
Utility module for REST API requests to be used by other Patchwork JS files.
diff --git a/htdocs/css/style.css b/htdocs/css/style.css
index 9156aa6ee..0f4f8917c 100644
--- a/htdocs/css/style.css
+++ b/htdocs/css/style.css
@@ -17,7 +17,7 @@ h2 a, h2 span {
}
pre {
- line-height: 110%;
+ line-height: normal;
background-color: white;
border-radius: 0;
}
@@ -56,6 +56,34 @@ pre {
color: #999;
}
+ul.dropdown-menu > li > form {
+ display: block;
+ width: 100%;
+}
+
+ul.dropdown-menu > li > form > button {
+ /* taken from bootstrap's styling for '.dropdown-menu > li > a' */
+ background: none;
+ border: none;
+ cursor: pointer;
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: 400;
+ line-height: 1.42857143;
+ color: #333;
+ white-space: nowrap;
+ width: 100%;
+ text-align: left;
+}
+
+ul.dropdown-menu > li > form > button:hover {
+ /* taken from bootstrap's styling for '.dropdown-menu > li > a:hover' */
+ color: #262626;
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+
form {
padding: 0em;
margin: 0em;
@@ -134,25 +162,21 @@ table.pw-list > thead {
background-color: white;
}
-a.colinactive, a.colactive {
+a.col-inactive, a.col-active {
color: black;
text-decoration: none;
}
-a.colinactive:hover {
+a.col-inactive:hover {
color: red;
}
div.filters {
}
-div.patchforms {
- margin-top: 1em;
-}
-
/* list order manipulation */
-table.patchlist tr.draghover {
+table.patch-list tr.draghover {
background: #e8e8e8 !important;
}
@@ -173,7 +197,7 @@ input#reorder-change {
.paginator {
text-align: right;
clear: both;
- margin: 8px 0 15px;
+ margin: 16px 0;
}
.paginator .prev-na,
@@ -228,7 +252,7 @@ table.patch-meta tr th, table.patch-meta tr td {
}
/* checks forms */
-/* TODO(stephenfin): Merge this with 'div.patchform' rules */
+/* TODO(stephenfin): Merge this with 'div.patch-form' rules */
.checks {
border: 1px solid gray;
margin: 0.5em 1em;
@@ -316,6 +340,12 @@ table.patch-meta tr th, table.patch-meta tr td {
border: 0;
}
+.submission-message .action-icons {
+ margin-left: 16px;
+ display: flex;
+ gap: 4px;
+}
+
.patch-pull-url {
font-family: "DejaVu Sans Mono", fixed;
}
@@ -354,15 +384,15 @@ button[class^=comment-action] {
}
.quote {
- color: #007f00;
+ color: #365cb5;
}
-span.p_header { color: #2e8b57; font-weight: bold; }
-span.p_chunk { color: #a52a2a; font-weight: bold; }
-span.p_context { color: #a020f0; }
-span.p_add { color: #008b8b; }
-span.p_del { color: #6a5acd; }
-span.p_mod { color: #0000ff; }
+span.p_header { font-weight: bold; }
+span.p_chunk { color: #329fb0; font-weight: bold; }
+span.p_context { }
+span.p_add { color: #1b9d09; background-color: #edffed; }
+span.p_del { color: #c80101; background-color: #ffe2e2; }
+span.p_mod { color: #a020f0; }
.acked-by {
color: #2d4566;
@@ -405,16 +435,94 @@ table.bundlelist td
padding-right: 2em;
}
+.patch-list-actions {
+ width: 100%;
+ display: inline-flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+}
+
/* forms that appear for a patch */
-div.patchform {
- border: thin solid #080808;
- padding-left: 0.6em;
- padding-right: 0.6em;
- float: left;
- margin: 0.5em 5em 0.5em 10px;
+.patch-forms {
+ display: inline-flex;
+ flex-wrap: wrap;
+ margin: 16px 0;
+}
+
+div.patch-form {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+}
+
+#edit-note-form {
+ display: flex;
+ flex-direction: column;
+ gap: 10px; /* Space between form elements */
+ max-width: 100%;
}
-div.patchform h3 {
+#edit-note-form textarea {
+ flex-grow: 1; /* Allows textarea to grow */
+ width: 100%;
+ min-height: 150px;
+ resize: vertical; /* Allow vertical resizing */
+ padding: 10px;
+ box-sizing: border-box;
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ font-size: 1rem;
+}
+
+#edit-note-form .form-actions {
+ display: flex;
+ justify-content: flex-start; /* Align buttons to the left */
+ gap: 10px; /* Add space between buttons */
+}
+#create-note-form div:first-of-type {
+ display: flex;
+ flex-direction: column;
+}
+
+select[class^=change-property-], .archive-patch-select, .add-bundle {
+ padding: 4px;
+ margin-right: 8px;
+ box-sizing: border-box;
+ border-radius: 4px;
+ background-color: var(--light-color);
+}
+
+#patch-form-archive {
+ display: flex;
+ align-items: center;
+ margin-right: 4px;
+}
+
+#patch-form-archive > label {
+ margin: 0px;
+}
+
+#patch-form-archive > select, #patch-form-archive > input {
+ margin: 0px 4px 0px 4px;
+}
+
+.patch-form-submit {
+ font-weight: bold;
+ padding: 4px;
+}
+
+#patch-form-bundle, #add-to-bundle, #remove-bundle, #create-note {
+ margin-left: 16px;
+}
+
+.create-bundle {
+ padding: 4px;
+ margin-right: 8px;
+ box-sizing: border-box;
+ border-radius: 4px;
+}
+
+div.patch-form h3 {
margin-top: 0em;
margin-left: -0.6em;
margin-right: -0.6em;
@@ -424,21 +532,23 @@ div.patchform h3 {
font-size: 100%;
}
-div.patchform ul {
+div.patch-form ul {
list-style-type: none;
padding-left: 0.2em;
margin-top: 0em;
}
-/* forms */
-table.form {
+.create-bundle {
+ padding: 4px;
+ margin-right: 8px;
+ box-sizing: border-box;
+ border-radius: 4px;
}
span.help_text {
font-size: 80%;
}
-
table.form td {
padding: 0.6em;
vertical-align: top;
diff --git a/htdocs/js/bundle.js b/htdocs/js/bundle.js
index c969d0be4..2a721d0dd 100644
--- a/htdocs/js/bundle.js
+++ b/htdocs/js/bundle.js
@@ -6,8 +6,8 @@ function order_button_click(node)
{
var rows, form;
- form = $("#reorderform");
- rows = $("#patchlist").get(0).tBodies[0].rows;
+ form = $("#reorder-form");
+ rows = $("#patch-list").get(0).tBodies[0].rows;
if (rows.length < 1)
return;
@@ -35,18 +35,18 @@ function order_button_click(node)
$("#reorder\\-cancel").css("display", "inline");
/* show help text */
- $("#reorderhelp").text('Drag & drop rows to reorder');
+ $("#reorder-help").text('Drag & drop rows to reorder');
/* enable drag & drop on the patches list */
- $("#patchlist").tableDnD({
+ $("#patch-list").tableDnD({
onDragClass: 'dragging',
onDragStart: function() { dragging = true; },
onDrop: function() { dragging = false; }
});
/* replace zebra striping with hover */
- $("#patchlist tbody tr").css("background", "inherit");
- $("#patchlist tbody tr").hover(drag_hover_in, drag_hover_out);
+ $("#patch-list tbody tr").css("background", "inherit");
+ $("#patch-list tbody tr").hover(drag_hover_in, drag_hover_out);
}
editing_order = !editing_order;
diff --git a/htdocs/js/patch-list.js b/htdocs/js/patch-list.js
new file mode 100644
index 000000000..6be031a49
--- /dev/null
+++ b/htdocs/js/patch-list.js
@@ -0,0 +1,14 @@
+$( document ).ready(function() {
+ $("#patch-list").stickyTableHeaders();
+
+ $("#patch-list").checkboxes("range", true);
+
+ $("#check-all").change(function(e) {
+ if(this.checked) {
+ $("#patch-list > tbody").checkboxes("check");
+ } else {
+ $("#patch-list > tbody").checkboxes("uncheck");
+ }
+ e.preventDefault();
+ });
+});
diff --git a/patchwork/admin.py b/patchwork/admin.py
index d3bf8d29d..88334fe9b 100644
--- a/patchwork/admin.py
+++ b/patchwork/admin.py
@@ -13,6 +13,7 @@
from patchwork.models import Cover
from patchwork.models import CoverComment
from patchwork.models import DelegationRule
+from patchwork.models import Note
from patchwork.models import Patch
from patchwork.models import PatchComment
from patchwork.models import PatchRelation
@@ -130,6 +131,15 @@ class PatchCommentAdmin(admin.ModelAdmin):
admin.site.register(PatchComment, PatchCommentAdmin)
+class NoteAdmin(admin.ModelAdmin):
+ list_display = ('patch', 'submitter', 'created_at', 'updated_at')
+ search_fields = ('patch__name', 'submitter__name', 'submitter__email')
+ date_hierarchy = 'created_at'
+
+
+admin.site.register(Note, NoteAdmin)
+
+
class PatchInline(admin.StackedInline):
model = Patch
extra = 0
diff --git a/patchwork/api/note.py b/patchwork/api/note.py
new file mode 100644
index 000000000..352e77406
--- /dev/null
+++ b/patchwork/api/note.py
@@ -0,0 +1,143 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2024 Meta Platforms, Inc. and affiliates.
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+
+from rest_framework import permissions
+from rest_framework.generics import get_object_or_404
+from rest_framework.generics import CreateAPIView
+from rest_framework.generics import RetrieveUpdateDestroyAPIView
+from rest_framework.generics import ListAPIView
+from patchwork.api.patch import PatchSerializer
+from patchwork.api.user import UserDetailSerializer
+from patchwork.api.base import BaseHyperlinkedModelSerializer
+from patchwork.models import Note
+from patchwork.models import Patch
+
+
+class NoteSerializer(BaseHyperlinkedModelSerializer):
+ submitter = UserDetailSerializer(read_only=True)
+ patch = PatchSerializer(read_only=True)
+
+ class Meta:
+ model = Note
+ fields = [
+ 'id',
+ 'patch',
+ 'submitter',
+ 'content',
+ 'created_at',
+ 'updated_at',
+ 'maintainer_only',
+ ]
+ read_only_fields = [
+ 'id',
+ 'patch',
+ 'submitter',
+ 'created_at',
+ 'updated_at',
+ 'maintainer_only',
+ ]
+
+
+class NoteDetailPermission(permissions.BasePermission):
+ def has_permission(self, request, view):
+ if not request.user.is_authenticated:
+ return False
+
+ if request.method == 'POST':
+ patch = Patch.objects.get(id=view.kwargs['patch_id'])
+ return (
+ patch.project in request.user.profile.maintainer_projects.all()
+ )
+
+ note = Note.objects.get(id=view.kwargs['note_id'])
+ return (
+ note.patch.project
+ in request.user.profile.maintainer_projects.all()
+ )
+
+ def has_object_permission(self, request, view, obj):
+ if (
+ not obj.maintainer_only
+ and request.method in permissions.SAFE_METHODS
+ ):
+ return True
+
+ if request.method == 'POST':
+ patch = Patch.objects.get(id=view.kwargs['patch_id'])
+ return (
+ patch.project in request.user.profile.maintainer_projects.all()
+ )
+
+ note = Note.objects.get(id=view.kwargs['note_id'])
+ return (
+ note.patch.project
+ in request.user.profile.maintainer_projects.all()
+ )
+
+
+class NoteListPermission(permissions.BasePermission):
+ def has_permission(self, request, view):
+ if request.method in permissions.SAFE_METHODS:
+ return True
+ if not request.user.is_authenticated:
+ return False
+ patch = Patch.objects.get(id=view.kwargs['patch_id'])
+ return patch.project in request.user.profile.maintainer_projects.all()
+
+ def has_object_permission(self, request, view, obj):
+ if request.method in permissions.SAFE_METHODS:
+ return True
+
+
+class NoteMixin(object):
+ queryset = Note.objects.all()
+ serializer_class = NoteSerializer
+ lookup_field = 'patch_id'
+
+ def get_queryset(self):
+ patch_id = self.kwargs['patch_id']
+ get_object_or_404(Patch, id=patch_id)
+
+ return Note.objects.filter(patch=patch_id)
+
+
+class NoteDetail(NoteMixin, RetrieveUpdateDestroyAPIView):
+ permission_classes = [NoteDetailPermission]
+
+ def get_object(self):
+ queryset = self.filter_queryset(self.get_queryset())
+ note_id = self.kwargs.get('note_id')
+ instance = get_object_or_404(queryset, id=note_id)
+ self.check_object_permissions(self.request, instance)
+ return instance
+
+
+class NoteList(NoteMixin, CreateAPIView, ListAPIView):
+ ordering = 'id'
+ permission_classes = [NoteListPermission]
+
+ def get_queryset(self):
+ patch_queryset = Patch.objects.all()
+
+ queryset = super().get_queryset()
+ public_notes = queryset.filter(maintainer_only=False)
+ user_patches = patch_queryset.filter(
+ project__in=list(
+ self.request.user.profile.maintainer_projects.all()
+ )
+ )
+ maintainer_notes = queryset.filter(
+ maintainer_only=True, patch__in=list(user_patches)
+ )
+
+ return public_notes | maintainer_notes
+
+ def perform_create(self, serializer):
+ serializer.save(
+ submitter=self.request.user,
+ patch=Patch.objects.get(id=self.kwargs['patch_id']),
+ )
+ return super().perform_create(serializer)
diff --git a/patchwork/api/patch.py b/patchwork/api/patch.py
index 443c38226..8fd345681 100644
--- a/patchwork/api/patch.py
+++ b/patchwork/api/patch.py
@@ -85,6 +85,7 @@ class PatchListSerializer(BaseHyperlinkedModelSerializer):
mbox = SerializerMethodField()
series = SeriesSerializer(read_only=True)
comments = SerializerMethodField()
+ notes = SerializerMethodField()
check = SerializerMethodField()
checks = SerializerMethodField()
tags = SerializerMethodField()
@@ -108,6 +109,11 @@ def get_comments(self, patch):
reverse('api-patch-comment-list', kwargs={'patch_id': patch.id})
)
+ def get_notes(self, patch):
+ return self.context.get('request').build_absolute_uri(
+ reverse('api-patch-note-list', kwargs={'patch_id': patch.id})
+ )
+
def get_check(self, instance):
return instance.combined_check_state
@@ -173,6 +179,7 @@ class Meta:
'mbox',
'series',
'comments',
+ 'notes',
'check',
'checks',
'tags',
@@ -191,6 +198,7 @@ class Meta:
'mbox',
'series',
'comments',
+ 'notes',
'check',
'checks',
'tags',
diff --git a/patchwork/forms.py b/patchwork/forms.py
index ed06d0d15..6cbdf3510 100644
--- a/patchwork/forms.py
+++ b/patchwork/forms.py
@@ -12,6 +12,7 @@
from django.template.backends import django as django_template_backend
from patchwork.models import Bundle
+from patchwork.models import Note
from patchwork.models import Patch
from patchwork.models import State
from patchwork.models import UserProfile
@@ -64,8 +65,11 @@ class BundleForm(forms.ModelForm):
regex=r'^[^/]+$',
min_length=1,
max_length=50,
- label='Name',
+ required=False,
error_messages={'invalid': "Bundle names can't contain slashes"},
+ widget=forms.TextInput(
+ attrs={'class': 'create-bundle', 'placeholder': 'Bundle name'}
+ ),
)
class Meta:
@@ -76,12 +80,19 @@ class Meta:
class CreateBundleForm(BundleForm):
def clean_name(self):
name = self.cleaned_data['name']
+ if not name:
+ raise forms.ValidationError(
+ 'No bundle name was specified', code='invalid'
+ )
+
count = Bundle.objects.filter(
owner=self.instance.owner, name=name
).count()
if count > 0:
raise forms.ValidationError(
- 'A bundle called %s already exists' % name
+ 'A bundle called %(name)s already exists',
+ code='invalid',
+ params={'name': name},
)
return name
@@ -96,6 +107,35 @@ class DeleteBundleForm(forms.Form):
bundle_id = forms.IntegerField(widget=forms.HiddenInput)
+class CreateNoteForm(forms.ModelForm):
+ name = 'createnoteform'
+ form_name = forms.CharField(initial=name, widget=forms.HiddenInput)
+ content = forms.CharField(label='Content', widget=forms.Textarea)
+ maintainer_only = forms.BooleanField(
+ label='Maintainers Only', initial=True, widget=forms.CheckboxInput
+ )
+
+ class Meta:
+ model = Note
+ fields = ['content', 'maintainer_only']
+
+
+class EditNoteForm(forms.ModelForm):
+ name = 'editnoteform'
+ form_name = forms.CharField(initial=name, widget=forms.HiddenInput)
+ content = forms.CharField(label='Content', widget=forms.Textarea)
+
+ class Meta:
+ model = Note
+ fields = ['content', 'maintainer_only']
+
+
+class DeleteNoteForm(forms.Form):
+ name = 'deletenoteform'
+ form_name = forms.CharField(initial=name, widget=forms.HiddenInput)
+ note_id = forms.IntegerField(widget=forms.HiddenInput)
+
+
class EmailForm(forms.Form):
email = forms.EmailField(max_length=200)
@@ -129,19 +169,29 @@ class PatchForm(forms.ModelForm):
def __init__(self, instance=None, project=None, *args, **kwargs):
super(PatchForm, self).__init__(instance=instance, *args, **kwargs)
self.fields['delegate'] = forms.ModelChoiceField(
- queryset=_get_delegate_qs(project, instance), required=False
+ queryset=_get_delegate_qs(project, instance),
+ widget=forms.Select(attrs={'class': 'change-property-delegate'}),
+ required=False,
)
class Meta:
model = Patch
fields = ['state', 'archived', 'delegate']
+ widgets = {
+ 'state': forms.Select(attrs={'class': 'change-property-state'}),
+ 'archived': forms.CheckboxInput(
+ attrs={'class': 'archive-patch-check'}
+ ),
+ }
class OptionalModelChoiceField(forms.ModelChoiceField):
- no_change_choice = ('*', 'no change')
+ no_change_choice = ('*', 'No change')
to_field_name = None
- def __init__(self, *args, **kwargs):
+ def __init__(self, *args, placeholder, className, **kwargs):
+ self.no_change_choice = ('*', placeholder)
+ self.widget = forms.Select(attrs={'class': className})
super(OptionalModelChoiceField, self).__init__(
initial=self.no_change_choice[0], *args, **kwargs
)
@@ -174,6 +224,10 @@ def clean(self, value):
class OptionalBooleanField(forms.TypedChoiceField):
+ def __init__(self, className, *args, **kwargs):
+ self.widget = forms.Select(attrs={'class': className})
+ super(OptionalBooleanField, self).__init__(*args, **kwargs)
+
def is_no_change(self, value):
return value == self.empty_value
@@ -181,22 +235,31 @@ def is_no_change(self, value):
class MultiplePatchForm(forms.Form):
action = 'update'
archived = OptionalBooleanField(
+ className='archive-patch-select',
choices=[
- ('*', 'no change'),
- ('True', 'Archived'),
- ('False', 'Unarchived'),
+ ('*', 'No change'),
+ ('True', 'Archive'),
+ ('False', 'Unarchive'),
],
coerce=lambda x: x == 'True',
empty_value='*',
+ label='Archived',
)
def __init__(self, project, *args, **kwargs):
super(MultiplePatchForm, self).__init__(*args, **kwargs)
self.fields['delegate'] = OptionalModelChoiceField(
- queryset=_get_delegate_qs(project=project), required=False
+ queryset=_get_delegate_qs(project=project),
+ placeholder='Delegate to',
+ className='change-property-delegate',
+ label='Delegate to',
+ required=False,
)
self.fields['state'] = OptionalModelChoiceField(
- queryset=State.objects.all()
+ queryset=State.objects.all(),
+ placeholder='Change state',
+ className='change-property-state',
+ label='Change state',
)
def save(self, instance, commit=True):
diff --git a/patchwork/migrations/0047_add_database_indexes.py b/patchwork/migrations/0047_add_database_indexes.py
new file mode 100644
index 000000000..42c119979
--- /dev/null
+++ b/patchwork/migrations/0047_add_database_indexes.py
@@ -0,0 +1,18 @@
+import patchwork.fields
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('patchwork', '0046_patch_comment_events'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='patch',
+ name='hash',
+ field=patchwork.fields.HashField(
+ blank=True, db_index=True, max_length=40, null=True
+ ),
+ ),
+ ]
diff --git a/patchwork/migrations/0048_note.py b/patchwork/migrations/0048_note.py
new file mode 100644
index 000000000..3555b6fc6
--- /dev/null
+++ b/patchwork/migrations/0048_note.py
@@ -0,0 +1,59 @@
+# Generated by Django 5.1.2 on 2024-11-04 07:50
+
+import django.db.models.deletion
+import django.utils.timezone
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('patchwork', '0047_add_database_indexes'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Note',
+ fields=[
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'created_at',
+ models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ (
+ 'updated_at',
+ models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ ('content', models.TextField(blank=True)),
+ ('maintainer_only', models.BooleanField(default=True)),
+ (
+ 'patch',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='note',
+ related_query_name='note',
+ to='patchwork.patch',
+ ),
+ ),
+ (
+ 'submitter',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ ]
diff --git a/patchwork/migrations/0049_alter_note_created_at_alter_note_updated_at.py b/patchwork/migrations/0049_alter_note_created_at_alter_note_updated_at.py
new file mode 100644
index 000000000..7d1786071
--- /dev/null
+++ b/patchwork/migrations/0049_alter_note_created_at_alter_note_updated_at.py
@@ -0,0 +1,22 @@
+# Generated by Django 5.1.2 on 2024-12-16 13:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('patchwork', '0048_note'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='note',
+ name='created_at',
+ field=models.DateTimeField(auto_now_add=True),
+ ),
+ migrations.AlterField(
+ model_name='note',
+ name='updated_at',
+ field=models.DateTimeField(auto_now=True),
+ ),
+ ]
diff --git a/patchwork/models.py b/patchwork/models.py
index 9a619bc56..5e427df10 100644
--- a/patchwork/models.py
+++ b/patchwork/models.py
@@ -33,6 +33,14 @@ def validate_regex_compiles(regex_string):
raise ValidationError('Invalid regular expression entered!')
+class TimestampMixin(models.Model):
+ updated_at = models.DateTimeField(auto_now=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+
+ class Meta:
+ abstract = True
+
+
class Person(models.Model):
# properties
@@ -501,7 +509,7 @@ class Patch(SubmissionMixin):
)
state = models.ForeignKey(State, null=True, on_delete=models.CASCADE)
archived = models.BooleanField(default=False)
- hash = HashField(null=True, blank=True)
+ hash = HashField(null=True, blank=True, db_index=True)
# series metadata
@@ -823,6 +831,18 @@ class Meta:
]
+class Note(TimestampMixin, models.Model):
+ patch = models.ForeignKey(
+ Patch,
+ related_name='note',
+ related_query_name='note',
+ on_delete=models.CASCADE,
+ )
+ submitter = models.ForeignKey(User, on_delete=models.CASCADE)
+ content = models.TextField(null=False, blank=True)
+ maintainer_only = models.BooleanField(default=True)
+
+
class Series(FilenameMixin, models.Model):
"""A collection of patches."""
@@ -1059,7 +1079,6 @@ def __str__(self):
class Check(models.Model):
-
"""Check for a patch.
Checks store the results of any tests executed (or executing) for a
diff --git a/patchwork/templates/patchwork/bundles.html b/patchwork/templates/patchwork/bundles.html
index cc2ebf90d..6d65a7e8d 100644
--- a/patchwork/templates/patchwork/bundles.html
+++ b/patchwork/templates/patchwork/bundles.html
@@ -56,7 +56,7 @@
Bundles
Bundles are groups of related patches. You can create bundles by
selecting patches from a project, then using the 'create bundle' form
to give your bundle a name. Each bundle can be public or private; public
- bundles are given a persistent URL, based you your username and the name
+ bundles are given a persistent URL, based on your username and the name
of the bundle. Private bundles are only visible to you.
diff --git a/patchwork/templates/patchwork/partials/patch-forms.html b/patchwork/templates/patchwork/partials/patch-forms.html
new file mode 100644
index 000000000..452bc79b8
--- /dev/null
+++ b/patchwork/templates/patchwork/partials/patch-forms.html
@@ -0,0 +1,56 @@
+
+{% if not order.editable %}
{% if order.name == "name" %}
-
+
-
+
Patch
{% else %}
-{% if not order.editable %}
- Patch
-{% else %}
- Patch
+ Patch
{% endif %}
+{% else %}
+ Patch
{% endif %}
- Series
+ Series
@@ -98,72 +81,72 @@
+{% if not order.editable %}
{% if order.name == "date" %}
-
+
-
+
Date
{% else %}
-{% if not order.editable %}
- Date
-{% else %}
- Date
+ Date
{% endif %}
+{% else %}
+ Date
{% endif %}
+{% if not order.editable %}
{% if order.name == "submitter" %}
-
+
-
+
Submitter
{% else %}
-{% if not order.editable %}
-
+
Submitter
-{% else %}
- Submitter
{% endif %}
+{% else %}
+ Submitter
{% endif %}
+{% if not order.editable %}
{% if order.name == "delegate" %}
-
+
-
+
Delegate
{% else %}
-{% if not order.editable %}
- Delegate
-{% else %}
- Delegate
+ Delegate
{% endif %}
+{% else %}
+ Delegate
{% endif %}
+{% if not order.editable %}
{% if order.name == "state" %}
-
+
-
+
State
{% else %}
-{% if not order.editable %}
- State
-{% else %}
- State
+ State
{% endif %}
+{% else %}
+ State
{% endif %}