Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 180 additions & 20 deletions src/deltacode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from deltacode.models import File
from deltacode.models import Scan
from deltacode import utils
from scancode.resource import VirtualCodebase


from pkg_resources import get_distribution, DistributionNotFound
Expand All @@ -48,14 +49,27 @@ class DeltaCode(object):
the form of File objects) contained in those scans.
"""
def __init__(self, new_path, old_path, options):
self.new = Scan(new_path)
self.old = Scan(old_path)
try:
self.codebase1 = VirtualCodebase(new_path)
self.codebase2 = VirtualCodebase(old_path)
except :
pass

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to handle the exception properly

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaJuRG okay.
The exceptions were raised when we are getting some invalid sacn paths , and when we counter some some attributes like "fiingerprint" which VirtualCodebase is not supporting

self.new_files_count = 0 #keeps the count of the new file
self.old_files_count = 0 #keeps the count of old files
self.new_files = [] # a list of [[new file1:Original path],[new file2:Original Path],...]
self.old_files = [] # a list of [[old file1:Original path],[old file2:Original Path],...]
self.new_files_fingerprint = dict() # map of { {new_file1:fingerprint},{new_file2:fingerprint},...} it will be needed when we need the fingerprints
self.old_files_fingerprint = dict() # map of { {old_file1:fingerprint},{old_file2:fingerprint},...}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is all this stuff and why is it needed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.new_files_count and self.old_files_count is to keep the track of the new files ,and old files.
I have added this to make the computation of the statistics easier,otherwise we would have to enumerate the virtual codebase objects to get the cont every time.
self.new_files_fingerprint and self.old_files_fingerprint it keeps a mapping of Resource objects files path from codebase1 and codebase2 with respect to the fingerprints which would be used in similarity comparisons.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaJuRG self.new_files and self.old_files it is list of lists comprising of [Resource objects,with their original path] ,Now we need to keep the track of the original path in the align_scans for alignment of the files.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its fine to enumerate on the Virtualcodebase. Stats should be calculated in the end anyway, and optionally for the user. I hate having carrying around all these unneeded fields.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, codebase objects have counts already that we can use. There is no need to track this twice.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaJuRG , yes we can get rid of this self.new_files_count and self.old_files_count as they are already present in codebase objects as it is present in the headers of the codebase objects, but for the old files and new files and the fingerprint, I think it is better to have the enumeration done one time and cache those files in an array, else we will again need to enumerate it whenever required.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaJuRG used the counts from the codebase , removed the additional variables for the files_count

self.options = options
self.deltas = []
self.errors = []
self.stats = Stat(self.new.files_count, self.old.files_count)
self.enumerate_files_from_codebases()
self.stats = Stat(self.new_files_count, self.old_files_count)

if new_path != None and old_path != None:

if self.new.path != '' and self.old.path != '':
self.new_files_errors = []
self.old_files_errors = []
self.determine_delta()
self.determine_moved()
self.license_diff()
Expand All @@ -67,6 +81,46 @@ def __init__(self, new_path, old_path, options):
self.deltas.sort(key=lambda Delta: Delta.factors, reverse=False)
self.deltas.sort(key=lambda Delta: Delta.score, reverse=True)

def get_files(self,codebase,is_new):
"""
Walk through the codebase, then generate the resources it(including all files and its directories)
then we enumerate over this generated codebase to get file, and directories as (obj)
Now during the time of enumeration we append files in the self.new_files list and incremants out self.new_files_count
Similarly for old_files.
Now , we also maintain a map which maps from object path to its fingerprint.
This map will be required when we calculate the hamming distances and compare similarity.
"""
resources = codebase.walk_filtered(topdown=True)
for i,obj in enumerate(resources):
if is_new:
if obj.is_file:
# append in the new_files
self.new_files.append([obj,''])
# increment the new files count
self.new_files_count += 1
try :
self.new_files_fingerprint[obj.path] = obj.fingerprint
except AttributeError:
self.new_files_fingerprint[obj.path] = None
else:
if obj.is_file:
# append in the old files
self.old_files.append([obj,''])
# increment the old files count
self.old_files_count += 1
try:
self.old_files_fingerprint[obj.path] = obj.fingerprint
except AttributeError:
self.old_files_fingerprint[obj.path] = None

def enumerate_files_from_codebases(self):
"""
An method which call the utility function get_files for generating the codebase
"""
self.get_files(self.codebase1,is_new = True)
self.get_files(self.codebase2,is_new = False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function's name is unrelated to what it actually does.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaJuRG , I think interchanging the function names get_files and enumerate_files_from_codebases would better match with the situation

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed the function name



def align_scans(self):
"""
Seek to align the paths of a pair of files (File objects) in the pair
Expand All @@ -75,12 +129,15 @@ def align_scans(self):
which calls utils.align_trees().
"""
try:
utils.fix_trees(self.new.files, self.old.files)
utils.fix_trees(self.new_files, self.old_files)
except utils.AlignmentException:
for f in self.new.files:
f.original_path = f.path
for f in self.old.files:
f.original_path = f.path
# self.new_files is a list of type [[ScannedResourceObject,originalPath],...]
# initially all original path are set to empty string
# so this part actually sets the original paths
for f in self.new_files:
f[1] = f[0].path
for f in self.old_files:
f[1] = f[0].path

def similarity(self):
"""
Expand All @@ -93,12 +150,14 @@ def similarity(self):
for delta in self.deltas:
if delta.new_file == None or delta.old_file == None:
continue
new_fingerprint = delta.new_file.fingerprint
old_fingerprint = delta.old_file.fingerprint
# this extracts the fingerprint corresponding to the particular file path

new_fingerprint = self.new_files_fingerprint.get(delta.new_file.path,None)
old_fingerprint = self.old_files_fingerprint.get(delta.old_file.path,None)
if new_fingerprint == None or old_fingerprint == None:
continue
new_fingerprint = utils.bitarray_from_hex(delta.new_file.fingerprint)
old_fingerprint = utils.bitarray_from_hex(delta.old_file.fingerprint)
new_fingerprint = utils.bitarray_from_hex(self.new_files_fingerprint[delta.new_file.path])
old_fingerprint = utils.bitarray_from_hex(self.old_files_fingerprint[delta.old_file.path])
hamming_distance = utils.hamming_distance(new_fingerprint, old_fingerprint)
if hamming_distance > 0 and hamming_distance <= SIMILARITY_LIMIT:
delta.score += hamming_distance
Expand All @@ -112,8 +171,9 @@ def determine_delta(self):
"""
# align scan and create our index
self.align_scans()
new_index = self.new.index_files()
old_index = self.old.index_files()
# returns file index wrt to old and new files
new_index = utils.index_files(self.new_files)
old_index = utils.index_files(self.old_files)

# gathering counts to ensure no files lost or missing from our 'deltas' set
new_visited, old_visited = 0, 0
Expand Down Expand Up @@ -172,12 +232,12 @@ def determine_delta(self):
continue

# make sure everything is accounted for
if new_visited != self.new.files_count:
if new_visited != self.new_files_count:
self.errors.append(
'DeltaCode Warning: new_visited({}) != new_total({}). Assuming old scancode format.'.format(new_visited, self.new.files_count)
)

if old_visited != self.old.files_count:
if old_visited != self.old_files_count:
self.errors.append(
'DeltaCode Warning: old_visited({}) != old_total({}). Assuming old scancode format.'.format(old_visited, self.old.files_count)
)
Expand Down Expand Up @@ -237,6 +297,7 @@ def license_diff(self):
])

for delta in self.deltas:

utils.update_from_license_info(delta, unique_categories)

def copyright_diff(self):
Expand Down Expand Up @@ -323,7 +384,104 @@ def is_added(self):
if self.new_file and not self.old_file:
return True

def to_dict(self):
def copyrights_to_dict(self,file):
"""
Given a Copyright object, return an OrderedDict with the full
set of fields from the ScanCode 'copyrights' value.
"""
copyrightC = []
try :
copyrightC = file.copyrights
except AttributeError:
# arises when the ScannedResource do not have any license attribute
return []
if len(copyrightC) == 0:
return []
if isinstance(copyrightC[0],OrderedDict):
# all the copyright are in correct format
all_copyrights = []
for i in range(len(copyrightC)):
# we iterate over all the copyrights
statements = copyrightC[i].get("statements",None)
holders = copyrightC[i].get("holders",None)
d = OrderedDict([
('statements', statements),
('holders', holders)
])
all_copyrights.append(d)

return all_copyrights

def licenses_to_dict(self,file):
"""
Given a License object, return an OrderedDict with the full
set of fields from the ScanCode 'license' value.
"""
licenseL = []
try:
licenseL = file.licenses
except AttributeError:
# arises when the ScannedResource do not have any license attribute
return []

if len(licenseL) == 0:
return []
if isinstance(licenseL[0],OrderedDict):
# the licenses are in the correct format
all_licenses = []
for i in range(len(licenseL)):
# we iterate over all the licenses
key = licenseL[i].get("key",None)
score = licenseL[i].get("score",None)
short_key = licenseL[i].get("short_name",None)
category = licenseL[i].get("category",None)
owner = licenseL[i].get("owner",None)
d = OrderedDict([
('key', key),
('score', score),
('short_name', short_key),
('category', category),
('owner', owner)
])
all_licenses.append(d)
return all_licenses

def new_file_to_dict(self,deltacode):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not a method of the File class?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaJuRG , I will be moving all these methods(license and copyright) to the file class

# if self.new_file is not empty we return the new file attributes
if self.new_file:
return OrderedDict([
("path",self.new_file.path),
("type",self.new_file.type),
("name",self.new_file.name),
("size",self.new_file.size),
("sha1",self.new_file.sha1),
("fingerprint",deltacode.new_files_fingerprint.get(self.new_file.path,"")),
("original_path",self.new_file.path),
# since license itself has many sub fields so we obtain it from another utility function
("licenses",self.licenses_to_dict(self.new_file)),
# since copyright itself has many sub fields so we obtain it from another utility function
("copyrights",self.copyrights_to_dict(self.new_file))
])


def old_file_to_dict(self,deltacode):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we repeating this function?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MaJuRG , I will be truncating this unnecessary functions.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

combined redundant functions def old_file_to_dict(self,deltacode) and def new_file_to_dict(self,deltacode) , to a single function.

if self.old_file :
return OrderedDict([
("path",self.old_file.path),
("type",self.old_file.type),
("name",self.old_file.name),
("size",self.old_file.size),
("sha1",self.old_file.sha1),
("fingerprint",deltacode.old_files_fingerprint.get(self.old_file.path,"")),
("original_path",self.old_file.path),
# since license itself has many sub fields so we obtain it from another utility function
("licenses",self.licenses_to_dict(self.old_file)),
# since copyright itself has many sub fields so we obtain it from another utility function
("copyrights",self.copyrights_to_dict(self.old_file))
])


def to_dict(self,deltacode):
"""
Return an OrderedDict comprising the 'factors', 'score' and new and old
'path' attributes of the object.
Expand All @@ -342,8 +500,10 @@ def to_dict(self):
('status', self.status),
('factors', self.factors),
('score', self.score),
('new', new_file),
('old', old_file),
# receives the detail of the new file
('new', self.new_file_to_dict(deltacode)),
# receives the details of the old file
('old', self.old_file_to_dict(deltacode)),
])

class Stat(object):
Expand Down
Loading