3737from vulnerabilities .severity_systems import ScoringSystem
3838from vulnerabilities .utils import classproperty
3939from vulnerabilities .utils import get_reference_id
40+ from vulnerabilities .utils import is_commit
4041from vulnerabilities .utils import is_cve
41- from vulnerabilities .utils import nearest_patched_package
4242from vulnerabilities .utils import purl_to_dict
4343from vulnerabilities .utils import update_purl_version
4444
@@ -194,6 +194,60 @@ def from_url(cls, url):
194194 return cls (url = url )
195195
196196
197+ @dataclasses .dataclass (eq = True )
198+ @functools .total_ordering
199+ class CodeCommitData :
200+ commit_hash : str
201+ vcs_url : str
202+
203+ commit_author : Optional [str ] = None
204+ commit_message : Optional [str ] = None
205+ commit_date : Optional [datetime .datetime ] = None
206+
207+ def __post_init__ (self ):
208+ if not self .commit_hash :
209+ raise ValueError ("Commit must have a non-empty commit_hash." )
210+
211+ if not is_commit (self .commit_hash ):
212+ raise ValueError ("Commit must be a valid a commit_hash." )
213+
214+ if not self .vcs_url :
215+ raise ValueError ("Commit must have a non-empty vcs_url." )
216+ if not isinstance (self .commit_hash , str ):
217+ self .commit_hash = str (self .commit_hash )
218+
219+ def __lt__ (self , other ):
220+ if not isinstance (other , CodeCommitData ):
221+ return NotImplemented
222+ return self ._cmp_key () < other ._cmp_key ()
223+
224+ # TODO: Add cache
225+ def _cmp_key (self ):
226+ return (self .commit_hash , self .vcs_url , self .commit_author , self .commit_message )
227+
228+ def to_dict (self ) -> dict :
229+ """Return a normalized dictionary representation of the commit."""
230+ return {
231+ "commit_hash" : self .commit_hash ,
232+ "vcs_url" : self .vcs_url ,
233+ "commit_author" : self .commit_author ,
234+ "commit_message" : self .commit_message ,
235+ "commit_date" : self .commit_date ,
236+ }
237+
238+ @classmethod
239+ def from_dict (cls , data : dict ):
240+ """Create a Commit instance from a dictionary."""
241+ commit_date = data .get ("commit_date" )
242+ return cls (
243+ commit_hash = str (data .get ("commit_hash" , "" )),
244+ vcs_url = data .get ("vcs_url" , "" ),
245+ commit_author = data .get ("commit_author" ),
246+ commit_message = data .get ("commit_message" ),
247+ commit_date = datetime .datetime .fromisoformat (commit_date ) if commit_date else None ,
248+ )
249+
250+
197251class UnMergeablePackageError (Exception ):
198252 """
199253 Raised when a package cannot be merged with another one.
@@ -444,6 +498,8 @@ class AdvisoryData:
444498 date_published : Optional [datetime .datetime ] = None
445499 weaknesses : List [int ] = dataclasses .field (default_factory = list )
446500 severities : List [VulnerabilitySeverity ] = dataclasses .field (default_factory = list )
501+ fixed_by_commits : List [CodeCommitData ] = dataclasses .field (default_factory = list )
502+ affected_by_commits : List [CodeCommitData ] = dataclasses .field (default_factory = list )
447503 url : Optional [str ] = None
448504 original_advisory_text : Optional [str ] = None
449505
@@ -476,6 +532,12 @@ def to_dict(self):
476532 "severities" : [sev .to_dict () for sev in self .severities ],
477533 "date_published" : self .date_published .isoformat () if self .date_published else None ,
478534 "weaknesses" : self .weaknesses ,
535+ "affected_by_commits" : [
536+ affected_by_commit .to_dict () for affected_by_commit in self .affected_by_commits
537+ ],
538+ "fixed_by_commits" : [
539+ fixed_by_commit .to_dict () for fixed_by_commit in self .fixed_by_commits
540+ ],
479541 "url" : self .url if self .url else "" ,
480542 }
481543 return {
@@ -537,6 +599,19 @@ class AdvisoryDataV2:
537599 weaknesses : List [int ] = dataclasses .field (default_factory = list )
538600 url : Optional [str ] = None
539601
602+ # TODO
603+ # Update from_dict and to_dict methods
604+ # Update compute_checksum method
605+ # Update BaseV2 Importer Pipeline
606+ # Change related tests
607+ # Add tests for these newly introduced fields
608+ # have a strong test for insert_advisory_v2 method
609+ # CodeCommitData importer
610+ # remove commit_rank from CodeCommitData importer
611+
612+ fixed_by_commits : List [CodeCommitData ] = dataclasses .field (default_factory = list )
613+ affected_by_commits : List [CodeCommitData ] = dataclasses .field (default_factory = list )
614+
540615 def __post_init__ (self ):
541616 if self .date_published and not self .date_published .tzinfo :
542617 logger .warning (f"AdvisoryData with no tzinfo: { self !r} " )
@@ -559,6 +634,12 @@ def to_dict(self):
559634 "references" : [ref .to_dict () for ref in self .references ],
560635 "date_published" : self .date_published .isoformat () if self .date_published else None ,
561636 "weaknesses" : self .weaknesses ,
637+ "affected_by_commits" : [
638+ affected_by_commit .to_dict () for affected_by_commit in self .affected_by_commits
639+ ],
640+ "fixed_by_commits" : [
641+ fixed_by_commit .to_dict () for fixed_by_commit in self .fixed_by_commits
642+ ],
562643 "url" : self .url if self .url else "" ,
563644 }
564645
@@ -578,6 +659,14 @@ def from_dict(cls, advisory_data):
578659 if date_published
579660 else None ,
580661 "weaknesses" : advisory_data ["weaknesses" ],
662+ "affected_by_commits" : [
663+ CodeCommitData .from_dict (affected_by_commit )
664+ for affected_by_commit in advisory_data ["affected_by_commits" ]
665+ ],
666+ "fixed_by_commits" : [
667+ CodeCommitData .from_dict (fixed_by_commit )
668+ for fixed_by_commit in advisory_data ["fixed_by_commits" ]
669+ ],
581670 "url" : advisory_data .get ("url" ) or None ,
582671 }
583672 return cls (** transformed )
0 commit comments