#!/bin/env python3 # FindCaseClashAction.py # Given an input file with a list of files from case senstive p4d # Output the dir and file clashes - and print recommended action # The input file should be the output of: # sort -f filelist | uniq -Di > filedups.txt # So all files are in case insensitive sort order # Python 2.7/3.3 compatibility. from __future__ import print_function import sys from collections import defaultdict import P4 class ClashFinder: "Finds new directories in paths - seperate class for ease of testing" def __init__(self): self.dupCount = defaultdict(int) self.lpaths = defaultdict(list) self.dirpath = defaultdict(int) def findCommonPartInd(self, parts1, parts2): # Returns index for common part i = 1 while i <= len(parts1): p1 = "/".join(parts1[:i]) p2 = "/".join(parts2[:i]) if p1 != p2: return i-1 i += 1 return 0 def checkCommonDirs(self, parts1, parts2): # We know parts aren't the same - just confirm equal lengths if len(parts1) != len(parts2): print("Problem: '%s' '%s'" % ("/".join(parts1), "/".join(parts2))) return ind = self.findCommonPartInd(parts1, parts2) if ind == 0 or ind == len(parts1): print("/".join(parts1)) print("/".join(parts2)) return # Want one more than common part p1 = "/".join(parts1[:ind+1]) p2 = "/".join(parts2[:ind+1]) self.dupCount[p1] += 1 self.dupCount[p2] += 1 if p1 not in self.lpaths[p1.lower()]: self.lpaths[p1.lower()].append(p1) if p2 not in self.lpaths[p2.lower()]: self.lpaths[p1.lower()].append(p2) if ind+1 < len(parts1): self.dirpath[p1.lower()] = 1 # This method returns None if no new directory found, or the directory name def checkClash(self, paths): if len(paths) != 2: print("Error Not2: %s" % ",".join(paths)) return self.checkCommonDirs(paths[0][2:].split("/"), paths[1][2:].split("/")) def chooseDirToObliterate(self, fpath1, fpath2, chg1, chg2, fcount1, fcount2, present1, present2): if present1 > 0 and present2 == 0: return (fpath1, "dir other deleted") if present2 > 0 and present1 == 0: return (fpath2, "dir other deleted") if present1 == present2 == 0: if chg1['time'] > chg2['time']: return (fpath2, "dir other changed earlier") if chg2['time'] > chg1['time']: return (fpath1, "dir other changed earlier") return (fpath1, "dir both deleted same time") if present1 > present2: return (fpath2, "dir more files present than other") if present2 > present1: return (fpath1, "dir more files present than other") return ("unknown", "dir unknown") def printDirAction(self, file): "Find out dir paths" if file not in self.dirpath: print("IgnoringFile: %s" % file) return f1, f2 = ["//%s/..." % f for f in self.lpaths[file]] chg1 = self.p4.run_changes("-m1", "-ssubmitted", f1)[0] chg2 = self.p4.run_changes("-m1", "-ssubmitted", f2)[0] files1 = self.p4.run_files(f1) files2 = self.p4.run_files(f2) present1 = len([x for x in files1 if 'delete' not in x['action']]) present2 = len([x for x in files2 if 'delete' not in x['action']]) print("%s: %s" % (f1, chg1)) print(" present/deleted: %d/%d" % (present1, len(files1))) print("%s: %s" % (f2, chg2)) print(" present/deleted: %d/%d" % (present2, len(files2))) obl, reason = self.chooseDirToObliterate(f1, f2, chg1, chg2, len(files1), len(files2), present1, present2) print("Obliterate: %s\nReason: %s\n" % (obl, reason)) def chooseFileToObliterate(self, fstat1, fstat2): if "delete" in fstat1["headAction"] and not "delete" in fstat2["headAction"]: return (fstat1["depotFile"], "other deleted") if "delete" in fstat2["headAction"] and not "delete" in fstat1["headAction"]: return (fstat2["depotFile"], "other deleted") if "delete" in fstat1["headAction"] and "delete" in fstat2["headAction"]: if fstat1["headTime"] > fstat2["headTime"]: return (fstat2["depotFile"], "other deleted earlier") if fstat2["headTime"] > fstat1["headTime"]: return (fstat1["depotFile"], "other deleted earlier") return (fstat1["depotFile"], "both deleted same time") if fstat1["headTime"] > fstat2["headTime"]: return (fstat2["depotFile"], "other changed earlier") if fstat2["headTime"] > fstat1["headTime"]: return (fstat1["depotFile"], "other changed earlier") if fstat1["digest"] == fstat2["digest"]: return (fstat1["depotFile"], "both same content") return ("unknown", "unknown") def printFileAction(self, file): "Find out paths" if len(self.lpaths[file]) != 2: print("Error: CantHandle: %s" % ",".join(self.lpaths[file])) return if file in self.dirpath: print("IgnoringDir: %s" % file) return f1, f2 = ["//%s" % f for f in self.lpaths[file]] fstat1 = self.p4.run_fstat("-Ol", f1)[0] fstat2 = self.p4.run_fstat("-Ol", f2)[0] print("%s: %s" % (f1, fstat1)) print("%s: %s" % (f2, fstat2)) obl, reason = self.chooseFileToObliterate(fstat1, fstat2) print("Obliterate: %s\nReason: %s\n" % (obl, reason)) def printClashes(self): seen = {} self.p4 = P4.P4() self.p4.connect() for k in self.lpaths.keys(): for j in self.lpaths[k]: if self.dupCount[j] == 1 and k not in seen: self.printFileAction(k) seen[k] = 1 elif k not in seen: self.printDirAction(k) seen[k] = 1 if __name__ == '__main__': cf = ClashFinder() with open(sys.argv[1]) as f: lastline = None paths = [] for line in f: line = line.rstrip() if line.lower() == lastline: paths.append(line) else: if paths: cf.checkClash(paths) lastline = line.lower() paths = [line] cf.printClashes()