1616from functools import cached_property
1717from itertools import groupby
1818from operator import attrgetter
19+ from traceback import format_exc as traceback_format_exc
20+ from typing import List
1921from typing import Union
2022from urllib .parse import urljoin
2123
@@ -2927,17 +2929,19 @@ class ImpactedPackage(models.Model):
29272929
29282930 base_purl = models .CharField (
29292931 max_length = 500 ,
2930- blank = True ,
2932+ blank = False ,
29312933 help_text = "Version less PURL related to impacted range." ,
29322934 )
29332935
29342936 affecting_vers = models .TextField (
29352937 blank = True ,
2938+ null = True ,
29362939 help_text = "VersionRange expression for package vulnerable to this impact." ,
29372940 )
29382941
29392942 fixed_vers = models .TextField (
29402943 blank = True ,
2944+ null = True ,
29412945 help_text = "VersionRange expression for packages fixing the vulnerable package in this impact." ,
29422946 )
29432947
@@ -2953,6 +2957,18 @@ class ImpactedPackage(models.Model):
29532957 help_text = "Packages vulnerable to this impact." ,
29542958 )
29552959
2960+ affecting_commits = models .ManyToManyField (
2961+ "CodeCommit" ,
2962+ related_name = "affecting_commits_in_impacts" ,
2963+ help_text = "Commits introducing this impact." ,
2964+ )
2965+
2966+ fixed_by_commits = models .ManyToManyField (
2967+ "CodeCommit" ,
2968+ related_name = "fixing_commits_in_impacts" ,
2969+ help_text = "Commits fixing this impact." ,
2970+ )
2971+
29562972 created_at = models .DateTimeField (
29572973 auto_now_add = True ,
29582974 db_index = True ,
@@ -3065,6 +3081,41 @@ def get_or_create_from_purl(self, purl: Union[PackageURL, str]):
30653081
30663082 return package , is_created
30673083
3084+ def bulk_get_or_create_from_purls (self , purls : List [Union [PackageURL , str ]]):
3085+ """
3086+ Return new or existing Packages given ``purls`` list of PackageURL object or PURL string.
3087+ """
3088+ purl_strings = [str (p ) for p in purls ]
3089+ existing_packages = PackageV2 .objects .filter (package_url__in = purl_strings )
3090+ existing_purls = set (existing_packages .values_list ("package_url" , flat = True ))
3091+
3092+ all_packages = list (existing_packages )
3093+ packages_to_create = []
3094+ for purl in purls :
3095+ if str (purl ) in existing_purls :
3096+ continue
3097+
3098+ purl_dict = purl_to_dict (purl )
3099+ purl = PackageURL (** purl_dict )
3100+
3101+ normalized = normalize_purl (purl = purl )
3102+ for name , value in purl_to_dict (normalized ).items ():
3103+ setattr (self , name , value )
3104+
3105+ purl_dict ["package_url" ] = str (normalized )
3106+ purl_dict ["plain_package_url" ] = str (utils .plain_purl (normalized ))
3107+
3108+ packages_to_create .append (PackageV2 (** purl_dict ))
3109+
3110+ try :
3111+ new_packages = PackageV2 .objects .bulk_create (packages_to_create )
3112+ except Exception as e :
3113+ logging .error (f"Error creating PackageV2: { e } \n { traceback_format_exc ()} " )
3114+ return []
3115+
3116+ all_packages .extend (new_packages )
3117+ return all_packages
3118+
30683119 def only_vulnerable (self ):
30693120 return self ._vulnerable (True )
30703121
@@ -3334,3 +3385,32 @@ class AdvisoryExploit(models.Model):
33343385 @property
33353386 def get_known_ransomware_campaign_use_type (self ):
33363387 return "Known" if self .known_ransomware_campaign_use else "Unknown"
3388+
3389+
3390+ class CodeCommit (models .Model ):
3391+ """
3392+ A CodeCommit Represents a single VCS commit (e.g., Git) related to a ImpactedPackage.
3393+ """
3394+
3395+ commit_hash = models .CharField (max_length = 64 , help_text = "Unique commit identifier (e.g., SHA)." )
3396+ vcs_url = models .URLField (
3397+ max_length = 1024 , help_text = "URL of the repository containing the commit."
3398+ )
3399+
3400+ commit_rank = models .IntegerField (
3401+ default = 0 ,
3402+ help_text = "Rank of the commit to support ordering by commit. Rank "
3403+ "zero means the rank has not been defined yet" ,
3404+ )
3405+ commit_author = models .CharField (
3406+ max_length = 100 , null = True , blank = True , help_text = "Author of the commit."
3407+ )
3408+ commit_date = models .DateTimeField (
3409+ null = True , blank = True , help_text = "Timestamp indicating when this commit was created."
3410+ )
3411+ commit_message = models .TextField (
3412+ null = True , blank = True , help_text = "Commit message or description."
3413+ )
3414+
3415+ class Meta :
3416+ unique_together = ("commit_hash" , "vcs_url" )
0 commit comments