| // Copyright (C) 2017 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.googlesource.gerrit.plugins.findowners; |
| |
| import com.googlesource.gerrit.plugins.findowners.Util.Owner2Weights; |
| import com.googlesource.gerrit.plugins.findowners.Util.String2StringSet; |
| import com.googlesource.gerrit.plugins.findowners.Util.StringSet; |
| import java.nio.file.FileSystem; |
| import java.nio.file.FileSystems; |
| import java.nio.file.PathMatcher; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import org.eclipse.jgit.lib.Repository; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** Keep all information about owners and owned files. */ |
| class OwnersDb { |
| private static final Logger log = LoggerFactory.getLogger(OwnersDb.class); |
| |
| private Server server; // could be set to a mocked server in unit tests |
| |
| private int numOwners; // # of owners of all given files. |
| |
| String revision; // tip of branch revision, where OWENRS were found. |
| String2StringSet dir2Globs; // (directory) => file globs in the directory |
| String2StringSet owner2Paths; // (owner email) => owned dirs or file globs |
| String2StringSet path2Owners; // (directory or file glob) => owner emails |
| StringSet readDirs; // directories in which we have checked OWNERS |
| StringSet stopLooking; // directories where OWNERS has "set noparent" |
| |
| private void init(Server s) { |
| numOwners = -1; |
| revision = ""; |
| dir2Globs = new String2StringSet(); |
| owner2Paths = new String2StringSet(); |
| path2Owners = new String2StringSet(); |
| readDirs = new StringSet(); |
| stopLooking = new StringSet(); |
| server = (null != s) ? s : new Server(); |
| } |
| |
| OwnersDb(Server s) { |
| init(s); |
| } |
| |
| OwnersDb(Server s, String key, Repository repository, |
| String branch, Collection<String> files) { |
| init(s, key, repository, null, null, branch, files); |
| } |
| |
| OwnersDb(Server s, String key, String url, String project, |
| String branch, Collection<String> files) { |
| init(s, key, null, url, project, branch, files); |
| } |
| |
| int getNumOwners() { |
| return (numOwners >= 0) ? numOwners : owner2Paths.keySet().size(); |
| } |
| |
| private void countNumOwners(Collection<String> files) { |
| String2StringSet file2Owners = findOwners(files, null); |
| if (null != file2Owners) { |
| StringSet emails = new StringSet(); |
| for (String key : file2Owners.keySet()) { |
| for (String owner : file2Owners.get(key)) { |
| emails.add(owner); |
| } |
| } |
| numOwners = emails.size(); |
| } else { |
| numOwners = owner2Paths.keySet().size(); |
| } |
| } |
| |
| private static void addToMap(String2StringSet map, |
| String key, String value) { |
| if (null == map.get(key)) { |
| map.put(key, new StringSet()); |
| } |
| map.get(key).add(value); |
| } |
| |
| void addOwnerPathPair(String owner, String path) { |
| addToMap(owner2Paths, owner, path); |
| addToMap(path2Owners, path, owner); |
| if (path.length() > 0 && path.charAt(path.length() - 1) != '/') { |
| addToMap(dir2Globs, Util.getDirName(path) + "/", path); // A file glob. |
| } |
| } |
| |
| void addFile(String path, String file, String[] lines) { |
| int n = 0; |
| for (String line : lines) { |
| String error = Parser.parseLine(this, path, file, line, ++n); |
| if (null != error && server.getReportSyntaxError()) { |
| log.warn(error); |
| } |
| } |
| } |
| |
| private void addOwnerWeights( |
| ArrayList<String> paths, ArrayList<Integer> distances, |
| String file, String2StringSet file2Owners, Owner2Weights map) { |
| for (int i = 0; i < paths.size(); i++) { |
| StringSet owners = path2Owners.get(paths.get(i)); |
| if (null == owners) { |
| continue; |
| } |
| for (String name : owners) { |
| addToMap(file2Owners, file, name); |
| if (null == map) { |
| continue; |
| } |
| if (map.containsKey(name)) { |
| map.get(name).addFile(file, distances.get(i)); |
| } else { |
| map.put(name, new OwnerWeights(file, distances.get(i))); |
| } |
| } |
| } |
| } |
| |
| /** Quick method to find owner emails of every file. */ |
| String2StringSet findOwners(Collection<String> files) { |
| return findOwners(files, null); |
| } |
| |
| /** Returns owner emails of every file and set up ownerWeights. */ |
| String2StringSet findOwners(Collection<String> files, |
| Owner2Weights ownerWeights) { |
| return findOwners(files.toArray(new String[0]), ownerWeights); |
| } |
| |
| /** Returns true if path has '*' owner. */ |
| private boolean findStarOwner(String path, int distance, |
| ArrayList<String> paths, |
| ArrayList<Integer> distances) { |
| StringSet owners = path2Owners.get(path); |
| if (null != owners) { |
| paths.add(path); |
| distances.add(new Integer(distance)); |
| if (owners.contains("*")) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** Returns owner emails of every file and set up ownerWeights. */ |
| String2StringSet findOwners(String[] files, Owner2Weights ownerWeights) { |
| // Returns a map of file to set of owner emails. |
| // If ownerWeights is not null, add to it owner to distance-from-dir; |
| // a distance of 1 is the lowest/closest possible distance |
| // (which makes the subsequent math easier). |
| String2StringSet file2Owners = new String2StringSet(); |
| for (String fileName : files) { |
| fileName = Util.normalizedFilePath(fileName); |
| String dirPath = Util.normalizedDirPath(fileName); |
| String baseName = fileName.substring(dirPath.length() + 1); |
| int distance = 1; |
| FileSystem fileSystem = FileSystems.getDefault(); |
| // Collect all matched (path, distance) in all OWNERS files for |
| // fileName. Add them only if there is no special "*" owner. |
| ArrayList<String> paths = new ArrayList<String>(); |
| ArrayList<Integer> distances = new ArrayList<Integer>(); |
| boolean foundStar = false; |
| while (true) { |
| int savedSizeOfPaths = paths.size(); |
| if (dir2Globs.containsKey(dirPath + "/")) { |
| StringSet patterns = dir2Globs.get(dirPath + "/"); |
| for (String pat : patterns) { |
| PathMatcher matcher = fileSystem.getPathMatcher("glob:" + pat); |
| if (matcher.matches(Paths.get(dirPath + "/" + baseName))) { |
| foundStar |= findStarOwner(pat, distance, paths, distances); |
| // Do not break here, a file could match multiple globs |
| // with different owners. |
| // OwnerWeights.add won't add duplicated files. |
| } |
| } |
| // NOTE: A per-file directive can only specify owner emails, |
| // not "set noparent". |
| } |
| // If baseName does not match per-file glob, paths is not changed. |
| // Then we should check the general non-per-file owners. |
| if (paths.size() == savedSizeOfPaths) { |
| foundStar |= findStarOwner(dirPath + "/", distance, paths, distances); |
| } |
| if (foundStar // This file can be approved by anyone, no owner. |
| || stopLooking.contains(dirPath + "/") // stop looking parent |
| || !dirPath.contains("/") /* root */ ) { |
| break; |
| } |
| if (paths.size() != savedSizeOfPaths) { |
| distance++; // increase distance for each found OWNERS |
| } |
| dirPath = Util.getDirName(dirPath); // go up one level |
| } |
| if (!foundStar) { |
| addOwnerWeights(paths, distances, fileName, |
| file2Owners, ownerWeights); |
| } |
| } |
| return file2Owners; |
| } |
| |
| private void init( |
| Server s, String key, Repository repository, String url, |
| String project, String branch, Collection<String> files) { |
| init(s); |
| for (String fileName : files) { |
| // Find OWNERS in fileName's directory and parent directories. |
| // Stop looking for a parent directory if OWNERS has "set noparent". |
| fileName = Util.normalizedFilePath(fileName); |
| String dir = Util.normalizedDirPath(fileName); |
| while (!readDirs.contains(dir)) { |
| readDirs.add(dir); |
| String content = server.getOWNERS(dir, repository, url, |
| project, branch); |
| if (null != content && !content.equals("")) { |
| addFile(dir + "/", dir + "/OWNERS", content.split("\\R+")); |
| } |
| if (stopLooking.contains(dir + "/") || !dir.contains("/")) { |
| break; // stop looking through parent directory |
| } |
| dir = Util.getDirName(dir); // go up one level |
| } |
| } |
| countNumOwners(files); |
| revision = server.getBranchRevision(repository, url, project, branch); |
| } |
| } |