Source code for changes.models.source
import logging
from datetime import datetime
from sqlalchemy import Column, DateTime, ForeignKey, String
from sqlalchemy.orm import relationship
from sqlalchemy.schema import UniqueConstraint, ForeignKeyConstraint
from typing import Optional # NOQA
from uuid import uuid4
from changes.config import db
from changes.db.types.guid import GUID
from changes.db.types.json import JSONEncodedDict
logger = logging.getLogger(__name__)
[docs]class Source(db.Model):
"""
This is the object that actually represents the code we run builds against.
Essentially its a revision, with a UUID, and a possible patch_id. Rows
with null patch_ids are just revisions, and rows with patch_ids apply
the linked patch on top of the revision and run builds against the
resulting code.
Why the indirection? This is how we handle phabricator diffs: when we
want to create a build for a new diff, we add a row here with the diff's
parent revision sha (NOT the sha of the commit phabricator is trying to
land, since that will change every time we update the diff) and a row
to the patch table that contains the contents of the diff.
Side note: Whenever we create a source row from a phabricator diff, we
log json text to the data field with information like the diff id.
"""
id = Column(GUID, primary_key=True, default=uuid4)
repository_id = Column(GUID, ForeignKey('repository.id'), nullable=False)
patch_id = Column(GUID, ForeignKey('patch.id'), unique=True)
revision_sha = Column(String(40))
date_created = Column(DateTime, default=datetime.utcnow)
data = Column(JSONEncodedDict)
repository = relationship('Repository', innerjoin=False)
patch = relationship('Patch')
revision = relationship('Revision',
primaryjoin="and_(Revision.sha == foreign(Source.revision_sha), "
"Revision.repository_id == Source.repository_id)")
__tablename__ = 'source'
__table_args__ = (
ForeignKeyConstraint(
('repository_id', 'revision_sha'),
('revision.repository_id', 'revision.sha')
),
UniqueConstraint(
'repository_id', 'revision_sha', 'patch_id', name='unq_source_revision',
),
)
def __init__(self, **kwargs):
super(Source, self).__init__(**kwargs)
if self.id is None:
self.id = uuid4()
if self.date_created is None:
self.date_created = datetime.utcnow()
def generate_diff(self):
"""
This function tries to generate a diff for this source, returning
None if it fails.
"""
# type: () -> Optional[str]
diff = None
if self.patch:
diff = self.patch.diff
else:
vcs = self.repository.get_vcs()
if vcs:
try:
diff = vcs.export(self.revision_sha)
except Exception:
logger.exception('Error getting diff from VCS for source id %s', self.id.hex)
if isinstance(diff, bytes):
try:
diff = diff.decode('utf-8')
except UnicodeDecodeError:
logger.exception('Error parsing unicode for source id %s', self.id.hex)
diff = None
return diff
def is_commit(self):
return bool(self.patch_id is None and self.revision_sha)