Source code for patchlab.models

# SPDX-License-Identifier: GPL-2.0-or-later
import email
import os
import re

from django.conf import settings
from django.db import models

from patchwork.models import Project, Submission, validate_regex_compiles


[docs]class BridgedSubmission(models.Model): """ Information about Patchwork submissions we've bridged back and forth. This is necessary so that comments converted to email are properly threaded. Attributes: submission: A one-to-one relationship with a :class:`patchwork.models.Submission`. merge_request: The merge request ID in the Git forge. commit: The commit hash of the bridged submission, if the submission in question is a patch. May be null for comments. """ submission = models.OneToOneField( Submission, on_delete=models.CASCADE, primary_key=True ) merge_request = models.IntegerField(null=False, blank=False) commit = models.CharField(max_length=128, null=True, blank=True)
[docs]class GitForge(models.Model): """ Represents a Git forge being bridged to and from email. Attributes: project: A one-to-one relationship with a :class:`patchwork.models.Project`. host: The hostname of the Git forge; this is used for a uniqueness constraint on it combined with the forge ID and development branch. forge_id: The unique ID of the project in the Git forge. For Gitlab, this is prominently displayed on the project home page. subject_prefix: The subject prefix to prepend to patches being sent to the list when a pull request is opened against the branch. development_branch: The branch in the Git forge used for development; this is the branch pull requests are opened against. """ project = models.OneToOneField( Project, on_delete=models.CASCADE, related_name="git_forge" ) host = models.CharField( max_length=255, help_text="The hostname of the Git forge; this is used " "to find the correct configuration section in the python-gitlab.cfg file.", ) forge_id = models.IntegerField( help_text="The unique ID of the project in the Git forge. For Gitlab, " "this is prominently displayed on the project home page." ) class Meta: unique_together = [["host", "forge_id"]] indexes = [models.Index(fields=["host", "forge_id"])] def __str__(self): return ( f"{self.project.name} hosted on {self.host} as project " f" ID {self.project_id}" ) def __repr__(self): return ( f"GitForge(host={self.host}, forge_id={self.forge_id}, " f"project={repr(self.project)})" ) @property def repo_path(self): return os.path.join(settings.PATCHLAB_REPO_DIR, f"{self.host}-{self.forge_id}") def branch(self, submission: Submission) -> str: """ Get the correct git branch name for a submission. Raises: ValueError: if no forge can be found. """ msg = email.message_from_string(submission.headers) for branch in self.branches.all(): if re.match(branch.subject_match, msg["Subject"]): return branch.name else: raise ValueError(f"No branch matches {submission}")
[docs]class Branch(models.Model): """ Models multiple development branches within a single tree. For example, a tree might have a feature branch and a bugfix branch and patches may apply to only one or the other. This allows us to route patches based on subject prefixes to the appropriate branch. """ git_forge = models.ForeignKey( GitForge, on_delete=models.CASCADE, related_name="branches" ) subject_prefix = models.CharField( max_length=64, help_text="The prefix to include in emails in addition to " "'PATCHvN'. The default will just be PATCH.", ) subject_match = models.CharField( max_length=64, blank=True, default="", validators=[validate_regex_compiles], help_text="Regex to match the " "subject against if the Patchwork project maps to multiple development " "branches. That is, perhaps you have a project where patches are " "prefixed with PROJ FEATURE for features and PROJ BUGFIX for bugfixes " "and pull requests should be opened against different branches depending " "on whether the patch is a feature or a bugfix. Like the project match, " "this will be used with IGNORECASE and MULTILINE flags. The first git " "forge returned from the database with a matching rule is used.", ) name = models.CharField( max_length=255, default="master", help_text="The development branch name; " "This is used to determine the email prefix when bridging a merge request " "to email, as well as the correct branch to apply incoming patches to " "when bridging email to merge requests", ) class Meta: indexes = [models.Index(fields=["name"])] def __str__(self): return f"The '{self.name}' branch in {self.git_forge}" def __repr__(self): return ( f"Branch(name={self.name}, subject_match={self.subject_match}, " f"subject_prefix={self.subject_prefix}, " f"git_forge={repr(self.git_forge)})" )