Java tutorial
/* * Copyright 2014 The Kuali Foundation Licensed under the * Educational Community 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.osedu.org/licenses/ECL-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 org.kuali.student.git.model; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.kuali.student.common.io.IOUtils; import org.kuali.student.git.model.branch.large.LargeBranchNameProviderMapImpl; import org.kuali.student.git.model.branch.utils.GitBranchUtils; import org.kuali.student.git.model.branch.utils.GitBranchUtils.ILargeBranchNameProvider; import org.kuali.student.git.model.tree.GitTreeNodeData; import org.kuali.student.git.model.tree.JGitTreeData; import org.kuali.student.git.model.tree.utils.GitTreeProcessor; import org.kuali.student.git.model.tree.utils.JGitTreeUtils; import org.kuali.student.svn.model.ExternalModuleInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Kuali Student Team * */ public class ExternalModuleUtils { private static final String REMAINDER = "remainder"; private static final String FUSION_MAVEN_PLUGIN_DAT = "fusion-maven-plugin.dat"; private static final String HTTPS_URL = "https://"; private static final String HTTP_URL = "http://"; private static final Logger log = LoggerFactory.getLogger(ExternalModuleUtils.class); /** * Consume the full content of the input stream and parse out the svn:externals property content found. * * You should use a bounded input stream sized for only the length of the svn:externals data to be consumed. * * @param inputStream * @return the list of brance merge info * @throws IOException */ public static List<ExternalModuleInfo> extractExternalModuleInfoFromSvnExternalsInputStream(long revision, String repositoryPrefixPath, InputStream inputStream) throws IOException { StringBuilder builder = new StringBuilder(); while (true) { String line = IOUtils.readLine(inputStream, "UTF-8"); if (line == null) break; if (line.isEmpty()) continue; builder.append(line).append("\n"); } return extractExternalModuleInfoFromSvnExternalsString(revision, repositoryPrefixPath, builder.toString()); } public static interface IBranchHeadProvider { public ObjectId getBranchHeadObjectId(String branchName); } public static String createFusionMavenPluginDataFileString(long currentRevision, final Repository repo, List<ExternalModuleInfo> externals, ILargeBranchNameProvider largeBranchNameProvider) { return createFusionMavenPluginDataFileString(currentRevision, new IBranchHeadProvider() { @Override public ObjectId getBranchHeadObjectId(String branchName) { ObjectId branchHead = null; try { // use the branch head Ref branchRef = repo.getRef(Constants.R_HEADS + branchName); if (branchRef != null) branchHead = branchRef.getObjectId(); else { log.warn("createFusionMavenPluginDataFileString failed to resolve branch for: {}", branchName); } } catch (IOException e) { // intentionally fall through } return branchHead; } }, externals, largeBranchNameProvider); } /** * Don't make any adjustments just render the externals into the fusion-maven-plugin.dat format as given. * * @param externals * @return fusion-maven-plugin.dat formatted string. */ public static String createFusionMavenPluginDataFileString(List<ExternalModuleInfo> externals) { StringBuilder builder = new StringBuilder(); for (ExternalModuleInfo external : externals) { String moduleName = external.getModuleName(); String externalBranchPath = external.getBranchPath(); long externalRevision = external.getRevision(); builder.append("# module = " + moduleName + " branch Path = " + externalBranchPath + " revision = " + externalRevision + "\n"); String branchName = external.getBranchName(); if (branchName == null) { log.warn("branchName is null for module = " + moduleName); } ObjectId branchHead = external.getBranchHeadId(); String subTreePath = external.getSubTreePath(); builder.append( moduleName + "::" + branchName + "::" + (branchHead == null ? "UNKNOWN" : branchHead.name()) + (subTreePath == null ? "" : "::" + subTreePath) + "\n"); } return builder.toString(); } /** * Create the fusion-maven-plugin.dat file from the externals given. Uses the branchHeadProvider to lookup and use the latest branch head id for each named branch. * * @param currentRevision * @param branchHeadProvider * @param externals * @param largeBranchNameProvider * @return */ public static String createFusionMavenPluginDataFileString(long currentRevision, IBranchHeadProvider branchHeadProvider, List<ExternalModuleInfo> externals, ILargeBranchNameProvider largeBranchNameProvider) { for (ExternalModuleInfo external : externals) { String branchName = external.getBranchName(); if (branchName == null) { branchName = GitBranchUtils.getCanonicalBranchName(external.getBranchPath(), external.getRevision(), largeBranchNameProvider); external.setBranchName(branchName); } ObjectId branchHead = branchHeadProvider.getBranchHeadObjectId(branchName); if (branchHead != null) { // store the branch head // this may overwrite the existing value but at is ok since this method is meant to update to the latest version. external.setBranchHeadId(branchHead); } } return createFusionMavenPluginDataFileString(externals); } public static List<ExternalModuleInfo> extractFusionMavenPluginData(InputStream input) throws IOException { List<String> lines = org.apache.commons.io.IOUtils.readLines(input); return extractFusionMavenPluginData(lines); } public static List<ExternalModuleInfo> extractFusionMavenPluginData(List<String> lines) { List<ExternalModuleInfo> externals = new ArrayList<>(); for (int i = 0; i < lines.size(); i += 2) { String commentLine = lines.get(i); String commentParts[] = commentLine.split("revision = "); String branchPathParts[] = commentParts[0].split("branch Path = "); String branchPath = branchPathParts[1].trim(); String revisionString = commentParts[1].trim(); String dataLine = lines.get(i + 1); String dataParts[] = dataLine.split("::"); String moduleName = dataParts[0].trim(); String branchName = dataParts[1].trim(); String branchHeadObjectId = dataParts[2].trim(); String subTreePath = null; if (dataParts.length == 4) subTreePath = dataParts[3].trim(); long revision = -1; try { revision = Long.parseLong(revisionString); } catch (NumberFormatException e) { // intentionally do nothing, use revision = 0 } ExternalModuleInfo emi = null; if (subTreePath != null) emi = new ExternalModuleInfo(moduleName, branchPath, branchName, revision, subTreePath); else emi = new ExternalModuleInfo(moduleName, branchPath, branchName, revision); if (!branchHeadObjectId.equals("UNKNOWN")) { emi.setBranchHeadId(ObjectId.fromString(branchHeadObjectId)); } externals.add(emi); } return externals; } /** * Extract from the svn:externals format string * * @param revision * @param repositoryPrefixPath * @param inputString * @return */ public static List<ExternalModuleInfo> extractExternalModuleInfoFromSvnExternalsString(long revision, String repositoryPrefixPath, String inputString) { boolean securePrefixPath = false; if (repositoryPrefixPath.startsWith(HTTPS_URL)) { securePrefixPath = true; } List<ExternalModuleInfo> externalsList = new LinkedList<>(); if (inputString == null) return externalsList; String lines[] = inputString.split("\n"); for (String line : lines) { if (line.isEmpty() || line.charAt(0) == '#') continue; // skip to the next line String[] parts = line.replace("\r", "").split(" "); if (parts.length != 2) continue; // skip to the next line int branchPathIndex = determineBranchPathIndex(parts); String moduleName = null; String branchPath = null; if (branchPathIndex == 0) { branchPath = parts[0].trim(); moduleName = parts[1].trim(); } else { branchPath = parts[1].trim(); moduleName = parts[0].trim(); } if (securePrefixPath && branchPath.startsWith(HTTP_URL)) { /* * Normalize to https if the prefix is also https. */ branchPath = HTTPS_URL + branchPath.substring(HTTP_URL.length()); } else if (!securePrefixPath && branchPath.startsWith(HTTPS_URL)) { /* * Normalize to http if the prefix starts with http. */ branchPath = HTTP_URL + branchPath.substring(HTTPS_URL.length()); } if (branchPath.startsWith(repositoryPrefixPath)) { // trim the leading slash if it exists. branchPath = branchPath.substring(repositoryPrefixPath.length() + 1); } else if (branchPath.startsWith("^")) { // relative external case if (branchPath.startsWith("/", 1)) { // trim ^/ from the front of the branch path branchPath = branchPath.substring(2); } else { // trim ^ from the front of the path branchPath = branchPath.substring(1); } } ExternalModuleInfo external = new ExternalModuleInfo(moduleName, branchPath, revision); externalsList.add(external); } return externalsList; } private static final Pattern detectBranchPartPattern = Pattern.compile("^(\\^|http:|https:)/.*"); /** * Figure out which part is the branch path part. * * It can include the url but since 1.5 it can also be relative. * */ public static boolean matchesBranchPart(String part) { Matcher m = detectBranchPartPattern.matcher(part); if (m == null) return false; return m.matches(); } private static int determineBranchPathIndex(String[] parts) { String firstCandidate = parts[0].trim(); String secondCandidate = parts[1].trim(); if (matchesBranchPart(firstCandidate)) return 0; else if (matchesBranchPart(secondCandidate)) return 1; else return -1; // neither matched. } /** * Create a new tree that is the result of the fusion of the existing commit with the other modules indicated in the externals list. * * We Resolve the branch heads using the SvnRevisionMapper * @param objectReader * @param inserter * @param rw * @param commit * @param externals * @param revisionMapper * @return * @throws MissingObjectException * @throws IncorrectObjectTypeException * @throws CorruptObjectException * @throws IOException */ public static AnyObjectId createFusedTree(ObjectReader objectReader, ObjectInserter inserter, RevWalk rw, RevCommit commit, List<ExternalModuleInfo> externals) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { // default is to include the fusion-maven-plugin.dat if it is in the directory. return createFusedTree(objectReader, inserter, rw, commit, externals, false); } public static AnyObjectId createFusedTree(ObjectReader objectReader, ObjectInserter inserter, RevWalk rw, RevCommit commit, List<ExternalModuleInfo> externals, boolean excludeFusionPluginData) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { List<JGitTreeData> baseData = JGitTreeUtils.extractBaseTreeLevel(objectReader, commit); for (ExternalModuleInfo externalModuleInfo : externals) { String moduleName = externalModuleInfo.getModuleName(); ObjectId referencedCommitId = externalModuleInfo.getBranchHeadId(); if (referencedCommitId != null) { RevCommit referencedCommit = rw.parseCommit(referencedCommitId); ObjectId sourceTreeId = referencedCommit.getTree().getId(); baseData.add(new JGitTreeData(moduleName, FileMode.TREE, sourceTreeId)); } else log.warn("unknown branch head object id for module {}", moduleName); } if (excludeFusionPluginData) { int targetIndex = -1; for (int i = 0; i < baseData.size(); i++) { JGitTreeData data = baseData.get(i); if (data.getName().equals(FUSION_MAVEN_PLUGIN_DAT)) { targetIndex = i; break; } } if (targetIndex != -1) baseData.remove(targetIndex); } ObjectId fusedTreeId = JGitTreeUtils.createTree(baseData, inserter); /* * */ return fusedTreeId; } /** * Look at the given commit and if there is a fusion-maven-plugin.dat in the root of its tree then load and return the contents. * * @param commit * @return the ExternalsModuleInfo's found, an empty list if none are found. * @throws IOException * @throws CorruptObjectException * @throws IncorrectObjectTypeException * @throws MissingObjectException */ public static List<ExternalModuleInfo> findExternalModulesForCommit(Repository repo, RevCommit commit) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { List<ExternalModuleInfo> modules = new LinkedList<ExternalModuleInfo>(); GitTreeProcessor treeProcessor = new GitTreeProcessor(repo); GitTreeNodeData tree = treeProcessor.extractExistingTreeData(commit.getTree().getId(), ""); ObjectId fusionDataBlobId = tree.find(repo, "fusion-maven-plugin.dat"); if (fusionDataBlobId == null) return modules; ObjectReader reader = repo.newObjectReader(); modules = ExternalModuleUtils .extractFusionMavenPluginData(reader.open(fusionDataBlobId, Constants.OBJ_BLOB).openStream()); reader.release(); return modules; } /** * Split the fused commit into modules.size() tree's for each ExternalModuleInfo provided. * * Put the remainder of the tree (original - external modules that were removed) under the key 'remainder'. * * @param fusedCommitId * @return a map of the branch name to split tree id. * @throws IOException * @throws IncorrectObjectTypeException * @throws MissingObjectException */ public static Map<String, ObjectId> splitFusedTree(ObjectReader objectReader, ObjectInserter inserter, RevWalk rw, ObjectId fusedCommitId, List<ExternalModuleInfo> modules) throws MissingObjectException, IncorrectObjectTypeException, IOException { Map<String, ObjectId> splitTreeMap = new HashMap<>(); RevCommit fusedCommit = rw.parseCommit(fusedCommitId); List<JGitTreeData> baseData = JGitTreeUtils.extractBaseTreeLevel(objectReader, fusedCommit); Iterator<JGitTreeData> iter = baseData.iterator(); while (iter.hasNext()) { JGitTreeData data = iter.next(); if (!data.getFileMode().equals(FileMode.TREE)) continue; String candidateName = data.getName(); // find the external if it exists for this name for (ExternalModuleInfo external : modules) { if (candidateName.equals(external.getModuleName())) { // match found ObjectId moduleTreeId = data.getObjectId(); splitTreeMap.put(external.getModuleName(), moduleTreeId); iter.remove(); break; } } } ObjectId remainderTreeId = JGitTreeUtils.createTree(baseData, inserter); splitTreeMap.put(REMAINDER, remainderTreeId); return splitTreeMap; } public static String createFusionMavenPluginDataFileString(Repository repo, List<ExternalModuleInfo> externals) { return createFusionMavenPluginDataFileString(0L, repo, externals, new LargeBranchNameProviderMapImpl()); } }