io.github.svndump_to_git.git.model.ExternalModuleUtils Maven / Gradle / Ivy
The newest version!
/*
* 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 io.github.svndump_to_git.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 io.github.svndump_to_git.git.model.tree.utils.GitTreeProcessor;
import io.github.svndump_to_git.svn.model.ExternalModuleInfo;
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 io.github.svndump_to_git.common.io.IOUtils;
import io.github.svndump_to_git.git.model.branch.large.LargeBranchNameProviderMapImpl;
import io.github.svndump_to_git.git.model.branch.utils.GitBranchUtils;
import io.github.svndump_to_git.git.model.branch.utils.GitBranchUtils.ILargeBranchNameProvider;
import io.github.svndump_to_git.git.model.tree.GitTreeNodeData;
import io.github.svndump_to_git.git.model.tree.JGitTreeData;
import io.github.svndump_to_git.git.model.tree.utils.JGitTreeUtils;
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 revision the subversion revision
* @param repositoryPrefixPath the prefix path of the subversion repository url
* @param inputStream the input stream containing the svn:externals data.
* @return the list of brance merge info
* @throws IOException on errors accessing the inputStream.
*/
public static ListextractExternalModuleInfoFromSvnExternalsInputStream (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, Listexternals, 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 The subversion externals to be fusesd
* @return fusion-maven-plugin.dat formatted string.
*/
public static String createFusionMavenPluginDataFileString(List 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 The current subversion revision
* @param branchHeadProvider Provides the way to get the tip of the branch for a given branch name.
* @param externals The list of subversoin externals
* @param largeBranchNameProvider The large branch name provider
* @return the content for the fusion-maven-plugin.dat file
*/
public static String createFusionMavenPluginDataFileString(long currentRevision, IBranchHeadProvider branchHeadProvider,
List 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 extractFusionMavenPluginData(InputStream input) throws IOException {
Listlines = org.apache.commons.io.IOUtils.readLines(input);
return extractFusionMavenPluginData(lines);
}
public static List extractFusionMavenPluginData(Listlines) {
Listexternals = 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 ListextractExternalModuleInfoFromSvnExternalsString (long revision, String repositoryPrefixPath, String inputString) {
boolean securePrefixPath = false;
if (repositoryPrefixPath.startsWith(HTTPS_URL)) {
securePrefixPath = true;
}
ListexternalsList = 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
* @return
* @throws MissingObjectException
* @throws IncorrectObjectTypeException
* @throws CorruptObjectException
* @throws IOException
*/
public static AnyObjectId createFusedTree(ObjectReader objectReader,
ObjectInserter inserter, RevWalk rw, RevCommit commit,
List 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 externals, boolean excludeFusionPluginData) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException {
List 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 ListfindExternalModulesForCommit (Repository repo, RevCommit commit) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException {
List modules = new LinkedList();
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.close();
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 splitFusedTree(ObjectReader objectReader, ObjectInserter inserter, RevWalk rw, ObjectId fusedCommitId, Listmodules) throws MissingObjectException, IncorrectObjectTypeException, IOException {
Map splitTreeMap = new HashMap<>();
RevCommit fusedCommit = rw.parseCommit(fusedCommitId);
List baseData = JGitTreeUtils.extractBaseTreeLevel(objectReader, fusedCommit);
Iterator 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 externals) {
return createFusionMavenPluginDataFileString(0L, repo, externals, new LargeBranchNameProviderMapImpl());
}
}