/*
* Copyright (c) 2013 TMate Software Ltd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* For information on how to redistribute this software under
* the terms of a license other than GNU General Public License
* contact TMate Software at support@hg4j.com
*/
package org.tmatesoft.hg.internal;
import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.tmatesoft.hg.core.HgFileRevision;
import org.tmatesoft.hg.core.HgIterateDirection;
import org.tmatesoft.hg.repo.HgDataFile;
import org.tmatesoft.hg.repo.HgRepository;
import org.tmatesoft.hg.repo.HgRuntimeException;
/**
* Traces file renames. Quite similar to HgChangesetFileSneaker, although the latter tries different paths
* to find origin names, while this class traces first renames found only.
*
* @author Artem Tikhomirov
* @author TMate Software Ltd.
*/
public final class FileRenameHistory {
private final int csetFrom;
private final int csetTo;
private final List<Chunk> history;
public FileRenameHistory(int csetStartIndex, int csetEndIndex) {
csetFrom = csetStartIndex;
csetTo = csetEndIndex;
history = new ArrayList<Chunk>(3);
}
public int startChangeset() {
return csetFrom;
}
public int endChangeset() {
return csetTo;
}
public boolean isOutOfRange(HgDataFile df, int fileRev) {
return df.getChangesetRevisionIndex(fileRev) < csetFrom || df.getChangesetRevisionIndex(0) > csetTo;
}
public void build(HgDataFile df, int fileRev) {
assert !isOutOfRange(df, fileRev);
LinkedList<Chunk> chunks = new LinkedList<Chunk>();
int chunkStart = 0, chunkEnd = fileRev;
int csetChunkEnd = -1, csetChunkStart = -1;
BasicRevMap csetMap = new BasicRevMap(0, fileRev).collect(df);
while (fileRev >= 0) {
int cset = csetMap.changesetAt(fileRev);
if (csetChunkEnd == -1) {
csetChunkEnd = cset;
}
if (cset <= csetFrom) {
chunkStart = fileRev;
csetChunkStart = csetFrom;
break;
}
if (cset > csetTo) {
chunkEnd = --fileRev;
csetChunkEnd = -1;
continue;
}
csetChunkStart = cset;
if (df.isCopy(fileRev)) {
chunks.addFirst(new Chunk(df, fileRev, chunkEnd, csetChunkStart, csetChunkEnd));
HgFileRevision origin = df.getCopySource(fileRev);
df = df.getRepo().getFileNode(origin.getPath());
fileRev = chunkEnd = df.getRevisionIndex(origin.getRevision());
csetMap = new BasicRevMap(0, fileRev).collect(df);
chunkStart = 0;
csetChunkEnd = cset - 1; // if df is copy, cset can't be 0
csetChunkStart = -1;
} else {
fileRev--;
}
}
assert chunkStart >= 0;
assert chunkEnd >= 0; // can be negative only if df.cset(0) > csetTo
assert csetChunkEnd >= 0;
assert csetChunkStart >= 0;
chunks.addFirst(new Chunk(df, chunkStart, chunkEnd, csetChunkStart, csetChunkEnd));
history.clear();
history.addAll(chunks);
}
public Iterable<Chunk> iterate(HgIterateDirection order) {
if (order == HgIterateDirection.NewToOld) {
return ReverseIterator.reversed(history);
}
assert order == HgIterateDirection.OldToNew;
return Collections.unmodifiableList(history);
}
public int chunks() {
return history.size();
}
public Chunk chunkAt(int cset) {
if (cset < csetFrom || cset > csetTo) {
return null;
}
for (Chunk c : history) {
if (c.firstCset() > cset) {
break;
}
if (cset <= c.lastCset()) {
return c;
}
}
return null;
}
/**
* file has changes [firstFileRev..lastFileRev] that have occurred somewhere in [firstCset..lastCset]
*/
public static final class Chunk {
private final HgDataFile df;
private final int fileRevFrom;
private final int fileRevTo;
private final int csetFrom;
private final int csetTo;
Chunk(HgDataFile file, int fileRevStart, int fileRevEnd, int csetStart, int csetEnd) {
df = file;
fileRevFrom = fileRevStart;
fileRevTo = fileRevEnd;
csetFrom = csetStart;
csetTo = csetEnd;
}
public HgDataFile file() {
return df;
}
public int firstFileRev() {
return fileRevFrom;
}
public int lastFileRev() {
return fileRevTo;
}
public int firstCset() {
return csetFrom;
}
public int lastCset() {
return csetTo;
}
}
private static final class BasicRevMap implements HgDataFile.LinkRevisionInspector {
private final int[] revs;
private final int fromRev;
private final int toRev;
public BasicRevMap(int startRev, int endRev) {
revs = new int[endRev+1]; // for simplicity, just ignore startRev now (it's 0 in local use anyway)
fromRev = startRev;
toRev = endRev;
Arrays.fill(revs, BAD_REVISION);
}
public BasicRevMap collect(HgDataFile df) {
df.indexWalk(fromRev, toRev, this);
return this;
}
public void next(int revisionIndex, int linkedRevisionIndex) throws HgRuntimeException {
revs[revisionIndex] = linkedRevisionIndex;
}
/**
* @return {@link HgRepository#BAD_REVISION} if there's no mapping
*/
public int changesetAt(int rev) {
return revs[rev];
}
}
}