summaryrefslogtreecommitdiffstats
path: root/meta/lib/oe/buildhistory_analysis.py
diff options
context:
space:
mode:
authorRichard Purdie <richard.purdie@linuxfoundation.org>2020-06-03 16:01:02 +0100
committerRichard Purdie <richard.purdie@linuxfoundation.org>2020-06-15 14:53:45 +0100
commiteb226b897fcba0ec1d94e15957e714d241931813 (patch)
tree585b5623a6b52bf9868e19801b78512ce21ea5fc /meta/lib/oe/buildhistory_analysis.py
parent26ae42ded7f2a59f7ccc8e8e8b763b4613a2bbdb (diff)
downloadpoky-eb226b897fcba0ec1d94e15957e714d241931813.tar.gz
buildhistory: Add simplistic file move detection
We'd like to use buildhistory more during patch review however its proving hard, particularly where whole subtrees of files move, such as a kernel version upgrade, or where a software module moves include directory. This adds file rename matching which covers our common case of library moves, kernel upgrades and more. A new test case is also added so that someone in the future can change the code and test the logic is still doing the expected things. (From OE-Core rev: 791ce304f5e066759874beac0feef5ee62a1c255) Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/lib/oe/buildhistory_analysis.py')
-rw-r--r--meta/lib/oe/buildhistory_analysis.py65
1 files changed, 62 insertions, 3 deletions
diff --git a/meta/lib/oe/buildhistory_analysis.py b/meta/lib/oe/buildhistory_analysis.py
index 5b28774c98..2d6fa1779e 100644
--- a/meta/lib/oe/buildhistory_analysis.py
+++ b/meta/lib/oe/buildhistory_analysis.py
@@ -213,6 +213,7 @@ class FileChange:
213 changetype_perms = 'P' 213 changetype_perms = 'P'
214 changetype_ownergroup = 'O' 214 changetype_ownergroup = 'O'
215 changetype_link = 'L' 215 changetype_link = 'L'
216 changetype_move = 'M'
216 217
217 def __init__(self, path, changetype, oldvalue = None, newvalue = None): 218 def __init__(self, path, changetype, oldvalue = None, newvalue = None):
218 self.path = path 219 self.path = path
@@ -251,10 +252,11 @@ class FileChange:
251 return '%s changed owner/group from %s to %s' % (self.path, self.oldvalue, self.newvalue) 252 return '%s changed owner/group from %s to %s' % (self.path, self.oldvalue, self.newvalue)
252 elif self.changetype == self.changetype_link: 253 elif self.changetype == self.changetype_link:
253 return '%s changed symlink target from %s to %s' % (self.path, self.oldvalue, self.newvalue) 254 return '%s changed symlink target from %s to %s' % (self.path, self.oldvalue, self.newvalue)
255 elif self.changetype == self.changetype_move:
256 return '%s moved to %s' % (self.path, self.oldvalue)
254 else: 257 else:
255 return '%s changed (unknown)' % self.path 258 return '%s changed (unknown)' % self.path
256 259
257
258def blob_to_dict(blob): 260def blob_to_dict(blob):
259 alines = [line for line in blob.data_stream.read().decode('utf-8').splitlines()] 261 alines = [line for line in blob.data_stream.read().decode('utf-8').splitlines()]
260 adict = {} 262 adict = {}
@@ -281,11 +283,14 @@ def file_list_to_dict(lines):
281 adict[path] = splitv[0:3] 283 adict[path] = splitv[0:3]
282 return adict 284 return adict
283 285
286numeric_removal = str.maketrans('0123456789', 'XXXXXXXXXX')
284 287
285def compare_file_lists(alines, blines, compare_ownership=True): 288def compare_file_lists(alines, blines, compare_ownership=True):
286 adict = file_list_to_dict(alines) 289 adict = file_list_to_dict(alines)
287 bdict = file_list_to_dict(blines) 290 bdict = file_list_to_dict(blines)
288 filechanges = [] 291 filechanges = []
292 additions = []
293 removals = []
289 for path, splitv in adict.items(): 294 for path, splitv in adict.items():
290 newsplitv = bdict.pop(path, None) 295 newsplitv = bdict.pop(path, None)
291 if newsplitv: 296 if newsplitv:
@@ -318,11 +323,65 @@ def compare_file_lists(alines, blines, compare_ownership=True):
318 if oldvalue != newvalue: 323 if oldvalue != newvalue:
319 filechanges.append(FileChange(path, FileChange.changetype_link, oldvalue, newvalue)) 324 filechanges.append(FileChange(path, FileChange.changetype_link, oldvalue, newvalue))
320 else: 325 else:
321 filechanges.append(FileChange(path, FileChange.changetype_remove)) 326 removals.append(path)
322 327
323 # Whatever is left over has been added 328 # Whatever is left over has been added
324 for path in bdict: 329 for path in bdict:
325 filechanges.append(FileChange(path, FileChange.changetype_add)) 330 additions.append(path)
331
332 # Rather than print additions and removals, its nicer to print file 'moves'
333 # where names or paths are similar.
334 revmap_remove = {}
335 for removal in removals:
336 translated = removal.translate(numeric_removal)
337 if translated not in revmap_remove:
338 revmap_remove[translated] = []
339 revmap_remove[translated].append(removal)
340
341 #
342 # We want to detect renames of large trees of files like
343 # /lib/modules/5.4.40-yocto-standard to /lib/modules/5.4.43-yocto-standard
344 #
345 renames = {}
346 for addition in additions.copy():
347 if addition not in additions:
348 continue
349 translated = addition.translate(numeric_removal)
350 if translated in revmap_remove:
351 if len(revmap_remove[translated]) != 1:
352 continue
353 removal = revmap_remove[translated][0]
354 commondir = addition.split("/")
355 commondir2 = removal.split("/")
356 idx = None
357 for i in range(len(commondir)):
358 if commondir[i] != commondir2[i]:
359 idx = i
360 break
361 commondir = "/".join(commondir[:i+1])
362 commondir2 = "/".join(commondir2[:i+1])
363 # If the common parent is in one dict and not the other its likely a rename
364 # so iterate through those files and process as such
365 if commondir2 not in bdict and commondir not in adict:
366 if commondir not in renames:
367 renames[commondir] = commondir2
368 for addition2 in additions.copy():
369 if addition2.startswith(commondir):
370 removal2 = addition2.replace(commondir, commondir2)
371 if removal2 in removals:
372 additions.remove(addition2)
373 removals.remove(removal2)
374 continue
375 filechanges.append(FileChange(removal, FileChange.changetype_move, addition))
376 additions.remove(addition)
377 removals.remove(removal)
378 for rename in renames:
379 filechanges.append(FileChange(renames[rename], FileChange.changetype_move, rename))
380
381 for addition in additions:
382 filechanges.append(FileChange(addition, FileChange.changetype_add))
383 for removal in removals:
384 filechanges.append(FileChange(removal, FileChange.changetype_remove))
326 385
327 return filechanges 386 return filechanges
328 387