#!/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()