Skip to content

Commit 2babaf4

Browse files
committed
Merge remote-tracking branch 'origin/git-fetchcode' into npm
# Conflicts: # vulnerabilities/importer.py # vulnerabilities/importers/__init__.py
2 parents fdfa4fa + ec88205 commit 2babaf4

5 files changed

Lines changed: 115 additions & 483 deletions

File tree

vulnerabilities/importer.py

Lines changed: 23 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import logging
1313
import os
1414
import shutil
15-
import tempfile
1615
import traceback
1716
import xml.etree.ElementTree as ET
1817
from pathlib import Path
@@ -23,9 +22,7 @@
2322
from typing import Set
2423
from typing import Tuple
2524

26-
from binaryornot.helpers import is_binary_string
27-
from git import DiffIndex
28-
from git import Repo
25+
from fetchcode.vcs import fetch_via_vcs
2926
from license_expression import Licensing
3027
from packageurl import PackageURL
3128
from univers.version_range import VersionRange
@@ -71,8 +68,8 @@ class Reference:
7168
severities: List[VulnerabilitySeverity] = dataclasses.field(default_factory=list)
7269

7370
def __post_init__(self):
74-
if not self.url:
75-
raise TypeError("Reference must have a url")
71+
if not any([self.url, self.reference_id]):
72+
raise TypeError
7673

7774
def normalized(self):
7875
severities = sorted(self.severities)
@@ -312,213 +309,38 @@ def advisory_data(self) -> Iterable[AdvisoryData]:
312309
raise NotImplementedError
313310

314311

315-
@dataclasses.dataclass
316-
class GitConfig:
317-
repository_url: str
318-
branch: Optional[str] = None
319-
create_working_directory: bool = True
320-
remove_working_directory: bool = True
321-
working_directory: Optional[str] = ""
322-
last_run_date: Optional[str] = None
323-
cutoff_date: Optional[str] = None
312+
class ForkError(Exception):
313+
pass
324314

325315

326-
# TODO: Needs rewrite
327316
class GitImporter(Importer):
328-
def __init__(self, config, cutoff_timestamp):
317+
def __init__(self, repo_url):
329318
super().__init__()
330-
self.config = config
331-
self.cutoff_timestamp = cutoff_timestamp
332-
333-
self._ensure_working_directory()
334-
self._ensure_repository()
335-
336-
def validate_configuration(self) -> None:
337-
if not self.config.create_working_directory and self.config.working_directory is None:
338-
self.error(
339-
'"create_working_directory" is not set but "working_directory" is set to '
340-
"the default, which calls tempfile.mkdtemp()"
341-
)
319+
self.repo_url = repo_url
320+
self.vcs_response = None
342321

343-
if not self.config.create_working_directory and not os.path.exists(
344-
self.config.working_directory
345-
):
346-
self.error(
347-
'"working_directory" does not contain an existing directory and'
348-
'"create_working_directory" is not set'
349-
)
322+
def __enter__(self):
323+
super().__enter__()
324+
self.clone()
325+
return self
350326

351-
if not self.config.remove_working_directory and self.config.working_directory is None:
352-
self.error(
353-
'"remove_working_directory" is not set and "working_directory" is set to '
354-
"the default, which calls tempfile.mkdtemp()"
355-
)
327+
def __exit__(self):
328+
self.vcs_response.delete()
356329

357-
def __exit__(self, exc_type, exc_val, exc_tb):
358-
if self.config.remove_working_directory:
359-
shutil.rmtree(self.config.working_directory)
330+
def clone(self):
331+
try:
332+
self.vcs_response = fetch_via_vcs(self.repo_url)
333+
except Exception as e:
334+
msg = f"Failed to fetch {self.repo_url} via vcs: {e}"
335+
logger.error(msg)
336+
raise ForkError(msg) from e
360337

361-
def file_changes(
362-
self,
363-
subdir: str = None,
364-
recursive: bool = False,
365-
file_ext: Optional[str] = None,
366-
) -> Tuple[Set[str], Set[str]]:
338+
def advisory_data(self) -> Iterable[AdvisoryData]:
367339
"""
368-
Returns all added and modified files since last_run_date or cutoff_date (whichever is more
369-
recent).
370-
:param subdir: filter by files in this directory
371-
:param recursive: whether to include files in subdirectories
372-
:param file_ext: filter files by this extension
373-
:return: The first set contains (absolute paths to) added files, the second one modified
374-
files
340+
Return AdvisoryData objects corresponding to the data being imported
375341
"""
376-
if subdir is None:
377-
working_dir = self.config.working_directory
378-
else:
379-
working_dir = os.path.join(self.config.working_directory, subdir)
380-
381-
path = Path(working_dir)
382-
383-
if self.config.last_run_date is None and self.config.cutoff_date is None:
384-
if recursive:
385-
glob = "**/*"
386-
else:
387-
glob = "*"
388-
389-
if file_ext:
390-
glob = f"{glob}.{file_ext}"
391-
392-
return {str(p) for p in path.glob(glob) if p.is_file()}, set()
393-
394-
return self._collect_file_changes(subdir=subdir, recursive=recursive, file_ext=file_ext)
395-
396-
def _collect_file_changes(
397-
self,
398-
subdir: Optional[str],
399-
recursive: bool,
400-
file_ext: Optional[str],
401-
) -> Tuple[Set[str], Set[str]]:
402-
403-
added_files, updated_files = set(), set()
404-
405-
# find the most ancient commit we need to diff with
406-
cutoff_commit = None
407-
for commit in self._repo.iter_commits(self._repo.head):
408-
if commit.committed_date < self.cutoff_timestamp:
409-
break
410-
cutoff_commit = commit
411-
412-
if cutoff_commit is None:
413-
return added_files, updated_files
414-
415-
def _is_binary(d: DiffIndex):
416-
return is_binary_string(d.b_blob.data_stream.read(1024))
417-
418-
for d in cutoff_commit.diff(self._repo.head.commit):
419-
if not _include_file(d.b_path, subdir, recursive, file_ext) or _is_binary(d):
420-
continue
421-
422-
abspath = os.path.join(self.config.working_directory, d.b_path)
423-
if d.new_file:
424-
added_files.add(abspath)
425-
elif d.a_blob and d.b_blob:
426-
if d.a_path != d.b_path:
427-
# consider moved files as added
428-
added_files.add(abspath)
429-
elif d.a_blob != d.b_blob:
430-
updated_files.add(abspath)
431-
432-
# Any file that has been added and then updated inside the window of the git history we
433-
# looked at, should be considered "added", not "updated", since it does not exist in the
434-
# database yet.
435-
updated_files = updated_files - added_files
436-
437-
return added_files, updated_files
438-
439-
def _ensure_working_directory(self) -> None:
440-
if self.config.working_directory is None:
441-
self.config.working_directory = tempfile.mkdtemp()
442-
elif self.config.create_working_directory and not os.path.exists(
443-
self.config.working_directory
444-
):
445-
os.mkdir(self.config.working_directory)
446-
447-
def _ensure_repository(self) -> None:
448-
if not os.path.exists(os.path.join(self.config.working_directory, ".git")):
449-
self._clone_repository()
450-
return
451-
self._repo = Repo(self.config.working_directory)
452-
453-
if self.config.branch is None:
454-
self.config.branch = str(self._repo.active_branch)
455-
branch = self.config.branch
456-
self._repo.head.reference = self._repo.heads[branch]
457-
self._repo.head.reset(index=True, working_tree=True)
458-
459-
remote = self._find_or_add_remote()
460-
self._update_from_remote(remote, branch)
461-
462-
def _clone_repository(self) -> None:
463-
kwargs = {}
464-
if self.config.branch:
465-
kwargs["branch"] = self.config.branch
466-
467-
self._repo = Repo.clone_from(
468-
self.config.repository_url, self.config.working_directory, **kwargs
469-
)
470-
471-
def _find_or_add_remote(self):
472-
remote = None
473-
for r in self._repo.remotes:
474-
if r.url == self.config.repository_url:
475-
remote = r
476-
break
477-
478-
if remote is None:
479-
remote = self._repo.create_remote(
480-
"added_by_vulnerablecode", url=self.config.repository_url
481-
)
482-
483-
return remote
484-
485-
def _update_from_remote(self, remote, branch) -> None:
486-
fetch_info = remote.fetch()
487-
if len(fetch_info) == 0:
488-
return
489-
branch = self._repo.branches[branch]
490-
branch.set_reference(remote.refs[branch.name])
491-
self._repo.head.reset(index=True, working_tree=True)
492-
493-
def advisory_data(self):
494342
raise NotImplementedError
495343

496-
def error(self, param):
497-
pass
498-
499-
500-
def _include_file(
501-
path: str,
502-
subdir: Optional[str] = None,
503-
recursive: bool = False,
504-
file_ext: Optional[str] = None,
505-
) -> bool:
506-
match = True
507-
508-
if subdir:
509-
if not subdir.endswith(os.path.sep):
510-
subdir = f"{subdir}{os.path.sep}"
511-
512-
match = match and path.startswith(subdir)
513-
514-
if not recursive:
515-
match = match and (os.path.sep not in path[len(subdir or "") :])
516-
517-
if file_ext:
518-
match = match and path.endswith(f".{file_ext}")
519-
520-
return match
521-
522344

523345
# TODO: Needs rewrite
524346
class OvalImporter(Importer):

vulnerabilities/importers/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from vulnerabilities.importers import nginx
1515
from vulnerabilities.importers import nvd
1616
from vulnerabilities.importers import openssl
17-
from vulnerabilities.importers import pypa
1817
from vulnerabilities.importers import pysec
1918
from vulnerabilities.importers import redhat
2019

@@ -27,8 +26,7 @@
2726
redhat.RedhatImporter,
2827
pysec.PyPIImporter,
2928
debian.DebianImporter,
30-
gitlab.GitLabAPIImporter,
31-
pypa.PyPaImporter,
29+
gitlab.GitLabGitImporter,
3230
]
3331

3432
IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY}

0 commit comments

Comments
 (0)