All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.svndump_to_git.git.model.SvnRevisionMapper 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 io.github.svndump_to_git.git.model.tree.utils.GitTreeProcessor;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import io.github.svndump_to_git.git.model.branch.utils.GitBranchUtils;
import io.github.svndump_to_git.git.model.branch.utils.GitBranchUtils.ILargeBranchNameProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * 
 * @author Kuali Student Team
 * 
 */
public class SvnRevisionMapper implements ILargeBranchNameProvider {

	private static final Logger log = LoggerFactory
			.getLogger(SvnRevisionMapper.class);

	private static final String REVISION_MAP_FILE_NAME = "revisions.map";

	private static final String REVISION_MAP_INDEX_FILE_NAME = "revisions.idx";

	private static final String REVISION_BRANCH_MERGE_FILE_NAME = "merge.map";

	private static final String REVISION_BRANCH_MERGE_INDEX_FILE_NAME = "merge.idx";

	public static class SvnRevisionMap {
		private long revision;
		private String branchName;
		private String branchPath;
		private String commitId;

		/**
		 * @param branchName
		 * @param commitId
		 */
		public SvnRevisionMap(long revision, String branchName,
				String branchPath, String commitId) {
			super();
			this.revision = revision;
			this.branchName = branchName;
			this.branchPath = branchPath;
			this.commitId = commitId;
		}

		/**
		 * @return the branchPath
		 */
		public String getBranchPath() {
			return branchPath;
		}

		/**
		 * @return the branchName
		 */
		public String getBranchName() {
			return branchName;
		}

		/**
		 * @return the commitId
		 */
		public ObjectId getCommitId() {

            if (commitId != null)
                return ObjectId.fromString(commitId);
            else
                return null;
		}

		/**
		 * @return the revision
		 */
		public long getRevision() {
			return revision;
		}

		/**
		 * @param revision
		 *            the revision to set
		 */
		public void setRevision(long revision) {
			this.revision = revision;
		}

	}

	private static class RevisionMapOffset {
		private long revision;
		private long startBtyeOffset;
		private long totalBytes;

		/**
		 * @param revision
		 * @param startBtyeOffset
		 * @param totalBytes
		 */
		public RevisionMapOffset(long revision, long startBtyeOffset,
				long totalBytes) {
			super();
			this.revision = revision;
			this.startBtyeOffset = startBtyeOffset;
			this.totalBytes = totalBytes;
		}

		/**
		 * @return the revision
		 */
		public long getRevision() {
			return revision;
		}

		/**
		 * @return the startBtyeOffset
		 */
		public long getStartBtyeOffset() {
			return startBtyeOffset;
		}

		/**
		 * @return the totalBytes
		 */
		public long getTotalBytes() {
			return totalBytes;
		}

	}

	private File revisonMappings;

	private TreeMap revisionMap = new TreeMap<>();

	private File revisionMapDataFile;

	private File revisionMapIndexFile;

	private PrintWriter revisionMapIndexWriter;

	private RandomAccessFile revisionMapDataRandomAccessFile;

	private long endOfRevisionMapDataFileInBytes;

	private File revisionBranchMergeDataFile;
	private File revisionBranchMergeIndexFile;

	private PrintWriter revisionBranchMergeIndexWriter;

	private RandomAccessFile revisionBranchMergeDataRandomAccessFile;

	private long endOfRevisionBranchMergeDataFileInBytes;

	private TreeMap> revisionMergeMap = new TreeMap<>();

	private GitTreeProcessor treeProcessor;

    /*
      For the target branch when loading this will have the most recent List of branches that have been
      already merged into the targetBranch.
     */
    private MapbranchNameToLatestRevisionMap = new LinkedHashMap<>();

	private static Comparator STRING_LONG_VALUE_COMPARATOR = new Comparator() {

		@Override
		public int compare(String o1, String o2) {
			Long l1 = Long.valueOf(o1);
			Long l2 = Long.valueOf(o2);

			return l1.compareTo(l2);
		}

	};

	private class MergeDataOffsetProvider implements RevisionMapOffsetProvider {

		private long revision;
		private String targetBranch;

		/**
		 * 
		 */
		public MergeDataOffsetProvider(long revision, String targetBranch) {
			super();
			this.revision = revision;
			this.targetBranch = targetBranch;
		}

		@Override
		public RevisionMapOffset getRevisionMapOffset() {

			Map map = revisionMergeMap.get(String
					.valueOf(revision));

			if (map == null)
				return null;

			return map.get(targetBranch);

		}

	}

	/**
	 * 
	 */
	public SvnRevisionMapper(Repository repo) {

		treeProcessor = new GitTreeProcessor(repo);

		revisonMappings = new File(repo.getDirectory(), "jsvn");

		revisonMappings.mkdirs();

		revisionMapDataFile = new File(revisonMappings, REVISION_MAP_FILE_NAME);

		revisionMapIndexFile = new File(revisonMappings,
				REVISION_MAP_INDEX_FILE_NAME);

		revisionBranchMergeDataFile = new File(revisonMappings,
				REVISION_BRANCH_MERGE_FILE_NAME);

		revisionBranchMergeIndexFile = new File(revisonMappings,
				REVISION_BRANCH_MERGE_INDEX_FILE_NAME);

	}

	public void initialize() throws IOException {

		// tracks the branch heads at each revision
		revisionMapDataRandomAccessFile = new RandomAccessFile(
				revisionMapDataFile, "rw");

		if (revisionMapIndexFile.exists()) {
			// load in any existing data.
			loadRevisionMapIndexData();
		}

		endOfRevisionMapDataFileInBytes = revisionMapDataFile.length();

		revisionMapIndexWriter = new PrintWriter(new FileOutputStream(
				revisionMapIndexFile, true));

		// tracks the merge info of each branch at each revision
		// used so we can compute the delta.
		revisionBranchMergeDataRandomAccessFile = new RandomAccessFile(
				revisionBranchMergeDataFile, "rwd");

		if (revisionBranchMergeIndexFile.exists()) {
			// load in any existing data.
			loadRevisionMergeIndexData();
		}

		endOfRevisionBranchMergeDataFileInBytes = revisionBranchMergeDataFile
				.length();

		revisionBranchMergeIndexWriter = new PrintWriter(new FileOutputStream(
				revisionBranchMergeIndexFile, true));

	}

	public void shutdown() throws IOException {

		revisionMapIndexWriter.flush();
		revisionMapIndexWriter.close();

		revisionMapDataRandomAccessFile.close();

		revisionBranchMergeIndexWriter.flush();
		revisionBranchMergeIndexWriter.close();

		revisionBranchMergeDataRandomAccessFile.close();

        branchNameToLatestRevisionMap.clear();

	}

	private void loadRevisionMergeIndexData() throws IOException {

		BufferedReader indexReader = new BufferedReader(new InputStreamReader(
				new FileInputStream(revisionBranchMergeIndexFile)));

		while (true) {

			String line = indexReader.readLine();

			if (line == null)
				break;

			String parts[] = line.split("::");

			if (parts.length != 4)
				continue;

			long revision = Long.parseLong(parts[0]);
			String targetBranch = parts[1];
			long byteStartOffset = Long.parseLong(parts[2]);
			long totalbytes = Long.parseLong(parts[3]);

            branchNameToLatestRevisionMap.put(targetBranch, Long.valueOf(revision));

			Map targetBranchOffsetMap = getRevisionMergeDataByTargetBranch(
					parts[0], true);

			targetBranchOffsetMap.put(targetBranch, new RevisionMapOffset(
					revision, byteStartOffset, totalbytes));

		}

		indexReader.close();
	}

	private Map getRevisionMergeDataByTargetBranch(
			String revisionString, boolean createIfDoesNotExist) {

		Map targetBranchOffsetMap = revisionMergeMap
				.get(revisionString);

		if (targetBranchOffsetMap == null && createIfDoesNotExist) {
			targetBranchOffsetMap = new HashMap();
			revisionMergeMap.put(revisionString, targetBranchOffsetMap);
		}

		return targetBranchOffsetMap;
	}

	private void loadRevisionMapIndexData() throws IOException {

		BufferedReader indexReader = new BufferedReader(new InputStreamReader(
				new FileInputStream(revisionMapIndexFile)));

		while (true) {

			String line = indexReader.readLine();

			if (line == null)
				break;

			String parts[] = line.split("::");

			if (parts.length != 3)
				continue;

			long revision = Long.parseLong(parts[0]);
			long byteStartOffset = Long.parseLong(parts[1]);
			long totalbytes = Long.parseLong(parts[2]);

			revisionMap.put(parts[0], new RevisionMapOffset(revision,
					byteStartOffset, totalbytes));

		}

		indexReader.close();

	}

	/*
	 * returns the total number of bytes written to the data file
	 */
	private long createRevisionEntry(RandomAccessFile dataFile,
			long endOfDataFileOffset, long revision, List revisionLines)
			throws IOException {

		OutputStream revisionMappingStream = null;

		ByteArrayOutputStream bytesOut;

		revisionMappingStream = new BZip2CompressorOutputStream(
				bytesOut = new ByteArrayOutputStream());

		PrintWriter pw = new PrintWriter(revisionMappingStream);

		IOUtils.writeLines(revisionLines, "\n", pw);

		pw.flush();

		pw.close();

		byte[] data = bytesOut.toByteArray();

		dataFile.seek(endOfDataFileOffset);

		dataFile.write(data);

		return data.length;
	}

	private void createRevisionMapEntry(long revision,
			List branchHeadLines) throws IOException {

		long bytesWritten = createRevisionEntry(
				revisionMapDataRandomAccessFile,
				endOfRevisionMapDataFileInBytes, revision, branchHeadLines);

		/*
		 * Write the number of bytes written for this revision.
		 */

		updateRevisionMapIndex(revision, endOfRevisionMapDataFileInBytes,
				bytesWritten);

		endOfRevisionMapDataFileInBytes += bytesWritten;

	}

	public void createRevisionMap(long revision, List branchHeads)
			throws IOException {

		List branchHeadLines = new ArrayList<>(branchHeads.size());

		for (Ref branchHead : branchHeads) {
			/*
			 * Only archive active branches. skip those containing @
			 */
			if (!branchHead.getName().contains("@"))
				branchHeadLines.add(revision + "::" + branchHead.getName()
						+ "::" + branchHead.getObjectId().name());
		}

		createRevisionMapEntry(revision, branchHeadLines);

	}

	private void updateRevisionMapIndex(long revision,
			long revisionStartByteIndex, long bytesWritten) {

		revisionMap.put(String.valueOf(revision), new RevisionMapOffset(
				revision, revisionStartByteIndex, bytesWritten));

		revisionMapIndexWriter.println(revision + "::" + revisionStartByteIndex
				+ "::" + bytesWritten);

		revisionMapIndexWriter.flush();
	}

	private void updateMergeDataIndex(long revision, String targetBranchName,
			List mergeInfo, long revisionStartByteIndex,
			long bytesWritten) {

		String revisionString = String.valueOf(revision);

		Map targetRevisionMap = getRevisionMergeDataByTargetBranch(
				revisionString, true);

		targetRevisionMap.put(targetBranchName, new RevisionMapOffset(revision,
				revisionStartByteIndex, bytesWritten));

		revisionBranchMergeIndexWriter.println(revision + "::"
				+ targetBranchName + "::" + revisionStartByteIndex + "::"
				+ bytesWritten);

		revisionBranchMergeIndexWriter.flush();
	}

	private void updateIndex(Map revisionMap,
			PrintWriter indexWriter, long revision,
			long revisionStartByteIndex, long bytesWritten) {
		revisionMap.put(String.valueOf(revision), new RevisionMapOffset(
				revision, revisionStartByteIndex, bytesWritten));

		indexWriter.println(revision + "::" + revisionStartByteIndex + "::"
				+ bytesWritten);

		indexWriter.flush();

	}

	/**
	 * Get the list of all references at the svn revision number given.
	 * 
	 * @param revision
	 * @return
	 * @throws IOException
	 */
	public List getRevisionHeads(long revision)
			throws IOException {

		InputStream inputStream = getRevisionInputStream(revision);

		if (inputStream == null)
			return null;

		List lines = IOUtils.readLines(inputStream, "UTF-8");

		inputStream.close();

		List revisionHeads = new ArrayList();

		for (String line : lines) {

            SvnRevisionMap revMap = extractSvnRevisionMapFromLine(revision, line);

            if (revMap != null)
                revisionHeads.add(revMap);


		}

		return revisionHeads;

	}

    private SvnRevisionMap extractSvnRevisionMapFromLine(long revision, String line) {

        String revisionString = String.valueOf(revision);

        String[] parts = line.split("::");

        if (!parts[0].equals(revisionString)) {
            log.warn(parts[0] + " is not a line for " + revisionString);
            return null;
        }

        String branchName = parts[1];

        String commitId = parts[2];

        String branchPath = GitBranchUtils.getBranchPath(branchName,
                revision, this);

        return (new SvnRevisionMap(revision, branchName,
                branchPath, commitId));
    }

    private InputStream getRevisionInputStream(final long revision)
			throws IOException {

		return getInputStream(new RevisionMapOffsetProvider() {

			@Override
			public RevisionMapOffset getRevisionMapOffset() {

				return revisionMap.get(String.valueOf(revision));
			}
		}, revisionMapDataRandomAccessFile);

	}

	private InputStream getMergeDataInputStream(final long revision,
			final String targetBranch) throws IOException {
		return getInputStream(new MergeDataOffsetProvider(revision,
				targetBranch), revisionBranchMergeDataRandomAccessFile);
	}

	private static interface RevisionMapOffsetProvider {
		public RevisionMapOffset getRevisionMapOffset();
	};

	private InputStream getInputStream(
			RevisionMapOffsetProvider offsetProvider, RandomAccessFile dataFile)
			throws IOException {

		RevisionMapOffset revisionOffset = offsetProvider
				.getRevisionMapOffset();

		if (revisionOffset == null)
			return null;

		byte[] data = new byte[(int) revisionOffset.getTotalBytes()];

		dataFile.seek(revisionOffset.getStartBtyeOffset());

		dataFile.readFully(data);

		return new BZip2CompressorInputStream(new ByteArrayInputStream(data));

	}

	/**
	 * Get the object id of the commit refered to by the branch at the revision
	 * given.
	 * 
	 * @param revision
	 * @param branchName
	 * @return
	 * @throws IOException
	 */
	public SvnRevisionMap getRevisionBranchHead(long revision, String branchName)
			throws IOException {

		InputStream inputStream = getRevisionInputStream(revision);

		if (inputStream == null)
			return null;

		List lines = IOUtils.readLines(inputStream, "UTF-8");

		inputStream.close();

		String adjustedBranchName = branchName;
		
		if (!adjustedBranchName.startsWith(Constants.R_HEADS))
			adjustedBranchName = Constants.R_HEADS + branchName;

		for (String line : lines) {

			String[] parts = line.split("::");

			if (parts[1].equals(adjustedBranchName)) {

                return extractSvnRevisionMapFromLine(revision, line);

			}

		}

		// this is actually an exceptional case
		// if not found it means that the reference can't be found.
		
		return null;
	}

	/*
	 * When we compute the list of revisions for a path its useful to know what
	 * the matched subpath was.
	 */
	public static class SvnRevisionMapResults {

		private String copyFromPath;

		private final SvnRevisionMap revMap;

		private final String subPath;

		public SvnRevisionMapResults(SvnRevisionMap revMap,
				String copyFromPath, String subPath) {
			this.revMap = revMap;
			this.copyFromPath = copyFromPath;
			this.subPath = subPath;
		}

		public SvnRevisionMapResults(SvnRevisionMap revMap, String copyFromPath) {
			this(revMap, copyFromPath, "");
		}

		/**
		 * @return the revMap
		 */
		public SvnRevisionMap getRevMap() {
			return revMap;
		}

		/**
		 * @return the subPath
		 */
		public String getSubPath() {
			return subPath;
		}

		/**
		 * @return the copyFromPath
		 */
		public String getCopyFromPath() {
			return copyFromPath;
		}

		/**
		 * @param copyFromPath
		 *            the copyFromPath to set
		 */
		public void setCopyFromPath(String copyFromPath) {
			this.copyFromPath = copyFromPath;
		}

	}

	public List getRevisionBranches(long targetRevision,
			String targetPath) throws IOException {

		ArrayList branches = new ArrayList<>();

		List heads = this.getRevisionHeads(targetRevision);

		if (heads == null)
			return branches;

		for (SvnRevisionMap revMap : heads) {

			SvnRevisionMapResults results = findResults(revMap, targetPath);

			if (results != null)
				branches.add(results);

		}

		return branches;
	}

	private SvnRevisionMapResults findResults(SvnRevisionMap revMap,
			String copyFromPath) {

		/*
		 * In most cases the match is because the copyFromPath is an actual
		 * branch.
		 * 
		 * In other cases it is a prefix that can match several branches
		 * 
		 * In a few cases it will refer to a branch and then a subpath within it
		 * it.
		 */
		String candidateBranchPath = revMap.getBranchPath().substring(
				Constants.R_HEADS.length());

		String candidateBranchParts[] = candidateBranchPath.split("\\/");

		String copyFromPathParts[] = copyFromPath.split("\\/");

		int smallestLength = Math.min(candidateBranchParts.length,
				copyFromPathParts.length);

		boolean allEquals = true;

		for (int i = 0; i < smallestLength; i++) {

			String candidatePart = candidateBranchParts[i];
			String copyFromPart = copyFromPathParts[i];

			if (!copyFromPart.equals(candidatePart)) {
				allEquals = false;
				break;
			}

		}

		if (allEquals) {

			if (copyFromPathParts.length > smallestLength) {
				// check inside of the branch for the rest of the path
				ObjectId commitId = revMap.getCommitId();

				String insidePath = StringUtils.join(copyFromPathParts, "/",
						smallestLength, copyFromPathParts.length);

				try {
					if (treeProcessor.treeContainsPath(commitId, insidePath)) {
						return new SvnRevisionMapResults(revMap, copyFromPath,
								insidePath);
					}
					// fall through
				} catch (Exception e) {
					log.error("Failed to find paths for commit {}", commitId);
					// fall through
				}
			} else {
				return new SvnRevisionMapResults(revMap, copyFromPath);
			}
		}

		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.kuali.student.git.utils.GitBranchUtils.ILargeBranchNameProvider#
	 * getBranchName(java.lang.String, long)
	 */
	@Override
	public String getBranchName(String longBranchId, long revision) {

		try {
			File revisionFile = new File(revisonMappings, "r" + revision
					+ "-large-branches");

			List lines = FileUtils.readLines(revisionFile, "UTF-8");

			for (String line : lines) {

				String[] parts = line.split("::");

				if (parts.length != 2) {
					continue;
				}

				if (parts[0].equals(longBranchId)) {
					return parts[1].trim();
				}

			}

			// not found
			return null;
		} catch (IOException e) {
			log.debug("failed to find longbranch for id = {}", longBranchId);
			return null;
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.kuali.student.git.utils.GitBranchUtils.ILargeBranchNameProvider#
	 * storeLargeBranchName(java.lang.String, java.lang.String, long)
	 */
	@Override
	public String storeLargeBranchName(String branchName, long revision) {

		try {
			ObjectId largeBranchNameId = GitBranchUtils
					.getBranchNameObjectId(branchName);

			String existingBranchName = getBranchName(largeBranchNameId.name(),
					revision);

			if (existingBranchName != null)
				return largeBranchNameId.getName();

			File revisionFile = new File(revisonMappings, "r" + revision
					+ "-large-branches");

			PrintWriter pw = new PrintWriter(new FileOutputStream(revisionFile,
					true));

			pw.println(largeBranchNameId.name() + "::" + branchName);

			pw.flush();
			pw.close();

			return largeBranchNameId.name();
		} catch (FileNotFoundException e) {
			log.warn("storeLargeBranchName: failed to open r" + revision
					+ "-large-branches");
			return null;
		}
	}

	public void repackMapFile() throws IOException {

		// close the data file
		revisionMapDataRandomAccessFile.close();

		// close the index file
		revisionMapIndexWriter.close();

		revisionMapIndexFile.delete();

		endOfRevisionMapDataFileInBytes = 0L;

		revisionMapIndexWriter = new PrintWriter(new FileOutputStream(new File(
				revisonMappings, REVISION_MAP_INDEX_FILE_NAME), true));

		// clear the in memory index
		revisionMap.clear();

		File copy = new File(revisonMappings, "repack-source.dat");

		FileUtils.copyFile(revisionMapDataFile, copy);

		revisionMapDataFile.delete();

		revisionMapDataRandomAccessFile = new RandomAccessFile(
				revisionMapDataFile, "rwd");

		BufferedReader reader = new BufferedReader(
				new InputStreamReader(new BZip2CompressorInputStream(
						new FileInputStream(copy), true)));

		String currentRevision = null;

		List currentRevisionHeads = new ArrayList();

		while (true) {

			String line = reader.readLine();

			if (line == null) {
				if (currentRevision != null) {
					// archive the last revision
					createRevisionMapEntry(Long.parseLong(currentRevision),
							currentRevisionHeads);

				}
				break;
			}

			String parts[] = line.split("::");

			String revisionString = parts[0];

			if (currentRevision == null)
				currentRevision = revisionString;

			if (!currentRevision.equals(revisionString)) {

				// write the revision data and update the index file
				createRevisionMapEntry(Long.parseLong(currentRevision),
						currentRevisionHeads);

				currentRevision = revisionString;

				currentRevisionHeads.clear();

			}

			currentRevisionHeads.add(line);

		}

		reader.close();

		copy.delete();

	}

	public void createMergeData(long revision, String targetBranch,
			List mergeInfo) throws IOException {

		List dataLines = new LinkedList<>();
		/*
		 * Format: revision :: target branch name :: merge branch name ::
		 * revision_1 , revision_2, .. revision_n.
		 */
		for (BranchMergeInfo bmi : mergeInfo) {

			List lineParts = new LinkedList<>();

			lineParts.add(String.valueOf(revision));

			lineParts.add(targetBranch);

			lineParts.add(bmi.getBranchName());

			lineParts.add(StringUtils.join(bmi.getMergedRevisions().iterator(),
					","));

			dataLines.add(StringUtils.join(lineParts, "::"));

		}

		long bytesWritten = createRevisionEntry(
				revisionBranchMergeDataRandomAccessFile,
				endOfRevisionBranchMergeDataFileInBytes, revision, dataLines);

		/*
		 * Write the number of bytes written for this revision.
		 */

		updateMergeDataIndex(revision, targetBranch, mergeInfo,
				endOfRevisionBranchMergeDataFileInBytes, bytesWritten);

		endOfRevisionBranchMergeDataFileInBytes += bytesWritten;

        branchNameToLatestRevisionMap.put(targetBranch, Long.valueOf(revision));

	}

	private BranchMergeInfo extractBranchMergeInfoFromLine(String branchName,
			String revisionParts[]) {

		BranchMergeInfo bmi = new BranchMergeInfo(branchName);

		for (String revisionString : revisionParts) {

			bmi.addMergeRevision(Long.valueOf(revisionString));
		}

		return bmi;

	}

    /**
     * Get the latest list of source merged revisions for the targetBranch named.  In some cases there can be gaps in the revision
     * numbers stored in the merge info file if there are commits that don't update the list of merged results.
     *
     */
    public List getLatestMergeBranches(String targetBranch) throws IOException {

        Long latestRevision = this.branchNameToLatestRevisionMap.get(targetBranch);

        if (latestRevision == null)
            return null;


        return getMergeBranches(latestRevision, targetBranch);

    }

    /**
	 * Get the list of branch merge info for the revision and target branch
	 * given.
	 * 
	 * @param revision
	 * @param targetBranch
	 * @return
	 * @throws IOException
	 */
	public List getMergeBranches(long revision,
			String targetBranch) throws IOException {

		List bmiList = new LinkedList<>();

		InputStream inputStream = getMergeDataInputStream(revision,
				targetBranch);

		if (inputStream == null)
			return null;

		List lines = IOUtils.readLines(inputStream, "UTF-8");

		inputStream.close();

		String revisionString = String.valueOf(revision);

		for (String line : lines) {

			String[] parts = line.split("::");

			if (!parts[0].equals(revisionString)) {
				log.warn(parts[0] + " is not a line for " + revisionString);
				continue;
			}

			String targetBranchName = parts[1];

			if (targetBranch.equals(targetBranchName)) {

				String mergeBranchName = parts[2];

				String mergedRevisionStrings[] = parts[3].split(",");

				BranchMergeInfo bmi = extractBranchMergeInfoFromLine(
						mergeBranchName, mergedRevisionStrings);

				bmiList.add(bmi);

			} else {
				log.warn(
						line
								+ " is not a valid line for revision {} and target branch {}",
						revision, targetBranch);
			}

		}

		return bmiList;

	}

	public Set getMergeBranchRevisions(long revision,
			String targetBranch, String mergeBranch) throws IOException {

		List bmiList = getMergeBranches(revision, targetBranch);

		for (BranchMergeInfo bmi : bmiList) {

			if (bmi.getBranchName().equals(mergeBranch)) {
				return bmi.getMergedRevisions();
			}
		}

		// no matches found.
		return new HashSet<>();

	}

	public void truncateTo(long longRevision) throws IOException {

		Map branchOffsets = revisionMergeMap
				.get(longRevision);

		long maxEndOfFile = endOfRevisionBranchMergeDataFileInBytes;

		if (branchOffsets != null) {
			for (RevisionMapOffset candidateOffset : branchOffsets.values()) {

				long candidateEndOfFile = candidateOffset.getStartBtyeOffset()
						+ candidateOffset.getTotalBytes();

				if (candidateEndOfFile > maxEndOfFile)
					maxEndOfFile = candidateEndOfFile;
			}
		}

		RevisionMapOffset revisionMapOffset = revisionMap.get(String
				.valueOf(longRevision));

		long revMapEndOfFile = revisionMapOffset.getStartBtyeOffset()
				+ revisionMapOffset.getTotalBytes();

		// now do the truncate

		endOfRevisionBranchMergeDataFileInBytes = maxEndOfFile;
		endOfRevisionMapDataFileInBytes = revMapEndOfFile;

		revisionBranchMergeDataRandomAccessFile
				.setLength(endOfRevisionBranchMergeDataFileInBytes);

		revisionMapDataRandomAccessFile
				.setLength(endOfRevisionMapDataFileInBytes);

		// reset the indices

		List revisions = new ArrayList<>();

		revisions.addAll(this.revisionMergeMap.keySet());

		Collections.sort(revisions, STRING_LONG_VALUE_COMPARATOR);

		int targetRevisionIndex = revisions.indexOf(String
				.valueOf(longRevision));

		Set keysToRemove = new HashSet<>(revisions.subList(
				targetRevisionIndex + 1, revisions.size()));

		for (String key : keysToRemove) {

			this.revisionMergeMap.remove(key);

		}

		revisions = new ArrayList<>(this.revisionMap.keySet());

		Collections.sort(revisions, STRING_LONG_VALUE_COMPARATOR);

		targetRevisionIndex = revisions.indexOf(String.valueOf(longRevision));

		keysToRemove = new HashSet<>(revisions.subList(targetRevisionIndex + 1,
				revisions.size()));

		for (String key : keysToRemove) {

			this.revisionMap.remove(key);

		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy