2626import shutil
2727import tempfile
2828import traceback
29+ import xml .etree .ElementTree as ET
30+ from binaryornot .helpers import is_binary_string
2931from datetime import datetime
3032from pathlib import Path
3133from typing import Any
3638from typing import Optional
3739from typing import Set
3840from typing import Tuple
39- import xml .etree .ElementTree as ET
40-
41- import pygit2
41+ from git import Repo , DiffIndex
4242from packageurl import PackageURL
4343from univers .version_specifier import VersionSpecifier
4444from univers .versions import version_class_by_package_type
@@ -318,35 +318,35 @@ def _collect_file_changes(
318318 file_ext : Optional [str ],
319319 ) -> Tuple [Set [str ], Set [str ]]:
320320
321- previous_commit = None
322321 added_files , updated_files = set (), set ()
323322
324- for commit in self . _repo . walk ( self . _repo . head . target , pygit2 . GIT_SORT_TIME ):
325- commit_time = commit . commit_time + commit . commit_time_offset # convert to UTC
326-
327- if commit_time < self .cutoff_timestamp :
323+ # find the most ancient commit we need to diff with
324+ cutoff_commit = None
325+ for commit in self . _repo . iter_commits ( self . _repo . head ):
326+ if commit . committed_date < self .cutoff_timestamp :
328327 break
328+ cutoff_commit = commit
329329
330- if previous_commit is None :
331- previous_commit = commit
332- continue
330+ if cutoff_commit is None :
331+ return added_files , updated_files
333332
334- for d in commit .tree .diff_to_tree (previous_commit .tree ).deltas :
335- if not _include_file (d .new_file .path , subdir , recursive , file_ext ) or d .is_binary :
336- continue
333+ def _is_binary (d : DiffIndex ):
334+ return is_binary_string (d .b_blob .data_stream .read (1024 ))
337335
338- abspath = os .path .join (self .config .working_directory , d .new_file .path )
339- # TODO
340- # Just filtering on the two status values for "added" and "modified" is too
341- # simplistic. This does not cover file renames, copies &
342- # deletions.
343- if d .status == pygit2 .GIT_DELTA_ADDED :
336+ for d in cutoff_commit .diff (self ._repo .head .commit ):
337+ if not _include_file (d .b_path , subdir , recursive , file_ext ) or _is_binary (d ):
338+ continue
339+
340+ abspath = os .path .join (self .config .working_directory , d .b_path )
341+ if d .new_file :
342+ added_files .add (abspath )
343+ elif d .a_blob and d .b_blob :
344+ if d .a_path != d .b_path :
345+ # consider moved files as added
344346 added_files .add (abspath )
345- elif d .status == pygit2 . GIT_DELTA_MODIFIED :
347+ elif d .a_blob != d . b_blob :
346348 updated_files .add (abspath )
347349
348- previous_commit = commit
349-
350350 # Any file that has been added and then updated inside the window of the git history we
351351 # looked at, should be considered "added", not "updated", since it does not exist in the
352352 # database yet.
@@ -363,29 +363,26 @@ def _ensure_working_directory(self) -> None:
363363 os .mkdir (self .config .working_directory )
364364
365365 def _ensure_repository (self ) -> None :
366- repodir = pygit2 .discover_repository (self .config .working_directory )
367- if repodir is None :
366+ if not os .path .exists (os .path .join (self .config .working_directory , ".git" )):
368367 self ._clone_repository ()
369368 return
370-
371- self ._repo = pygit2 .Repository (repodir )
369+ self ._repo = Repo (self .config .working_directory )
372370
373371 if self .config .branch is None :
374- self .config .branch = self ._repo .head .shorthand
375- branch = self ._repo .branches [self .config .branch ]
376-
377- if not branch .is_checked_out ():
378- self ._repo .checkout (branch )
372+ self .config .branch = str (self ._repo .active_branch )
373+ branch = self .config .branch
374+ self ._repo .head .reference = self ._repo .heads [branch ]
375+ self ._repo .head .reset (index = True , working_tree = True )
379376
380377 remote = self ._find_or_add_remote ()
381378 self ._update_from_remote (remote , branch )
382379
383380 def _clone_repository (self ) -> None :
384381 kwargs = {}
385382 if self .config .branch :
386- kwargs ["checkout_branch " ] = self .config .branch
383+ kwargs ["branch " ] = self .config .branch
387384
388- self ._repo = pygit2 . clone_repository (
385+ self ._repo = Repo . clone_from (
389386 self .config .repository_url , self .config .working_directory , ** kwargs
390387 )
391388
@@ -397,20 +394,19 @@ def _find_or_add_remote(self):
397394 break
398395
399396 if remote is None :
400- remote = self ._repo .remotes . create (
401- "added_by_vulnerablecode" , self .config .repository_url
397+ remote = self ._repo .create_remote (
398+ "added_by_vulnerablecode" , url = self .config .repository_url
402399 )
403400
404401 return remote
405402
406403 def _update_from_remote (self , remote , branch ) -> None :
407- progress = remote .fetch ()
408- if progress . received_objects == 0 :
404+ fetch_info = remote .fetch ()
405+ if len ( fetch_info ) == 0 :
409406 return
410-
411- remote_branch = self ._repo .branches [f"{ remote .name } /{ self .config .branch } " ]
412- branch .set_target (remote_branch .target )
413- self ._repo .checkout (branch , strategy = pygit2 .GIT_CHECKOUT_FORCE )
407+ branch = self ._repo .branches [branch ]
408+ branch .set_reference (remote .refs [branch .name ])
409+ self ._repo .head .reset (index = True , working_tree = True )
414410
415411
416412def _include_file (
0 commit comments