tikhomirov@596: /* tikhomirov@596: * Copyright (c) 2013 TMate Software Ltd tikhomirov@596: * tikhomirov@596: * This program is free software; you can redistribute it and/or modify tikhomirov@596: * it under the terms of the GNU General Public License as published by tikhomirov@596: * the Free Software Foundation; version 2 of the License. tikhomirov@596: * tikhomirov@596: * This program is distributed in the hope that it will be useful, tikhomirov@596: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@596: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@596: * GNU General Public License for more details. tikhomirov@596: * tikhomirov@596: * For information on how to redistribute this software under tikhomirov@596: * the terms of a license other than GNU General Public License tikhomirov@596: * contact TMate Software at support@hg4j.com tikhomirov@596: */ tikhomirov@596: package org.tmatesoft.hg.internal; tikhomirov@596: tikhomirov@596: import static org.tmatesoft.hg.core.HgIterateDirection.NewToOld; tikhomirov@596: tikhomirov@596: import java.util.Collections; tikhomirov@596: import java.util.LinkedList; tikhomirov@596: tikhomirov@596: import org.tmatesoft.hg.core.HgIterateDirection; tikhomirov@596: import org.tmatesoft.hg.core.Nodeid; tikhomirov@596: import org.tmatesoft.hg.repo.HgDataFile; tikhomirov@596: import org.tmatesoft.hg.repo.HgRepository; tikhomirov@596: tikhomirov@596: /** tikhomirov@596: * History of a file, with copy/renames, and corresponding revision information. tikhomirov@596: * Facility for file history iteration. tikhomirov@596: * tikhomirov@596: * FIXME [post-1.1] Utilize in HgLogCommand and anywhere else we need to follow file history tikhomirov@596: * tikhomirov@596: * @author Artem Tikhomirov tikhomirov@596: * @author TMate Software Ltd. tikhomirov@596: */ tikhomirov@596: public class FileHistory { tikhomirov@596: tikhomirov@596: private LinkedList fileCompleteHistory = new LinkedList(); tikhomirov@596: private final HgDataFile df; tikhomirov@596: private final int csetTo; tikhomirov@596: private final int csetFrom; tikhomirov@596: tikhomirov@596: public FileHistory(HgDataFile file, int fromChangeset, int toChangeset) { tikhomirov@596: df = file; tikhomirov@596: csetFrom = fromChangeset; tikhomirov@596: csetTo = toChangeset; tikhomirov@596: } tikhomirov@596: tikhomirov@596: public int getStartChangeset() { tikhomirov@596: return csetFrom; tikhomirov@596: } tikhomirov@596: tikhomirov@596: public int getEndChangeset() { tikhomirov@596: return csetTo; tikhomirov@596: } tikhomirov@596: tikhomirov@596: public void build() { tikhomirov@596: assert fileCompleteHistory.isEmpty(); tikhomirov@596: HgDataFile currentFile = df; tikhomirov@596: final int changelogRevIndexEnd = csetTo; tikhomirov@596: final int changelogRevIndexStart = csetFrom; tikhomirov@596: int fileLastClogRevIndex = changelogRevIndexEnd; tikhomirov@596: FileRevisionHistoryChunk nextChunk = null; tikhomirov@596: fileCompleteHistory.clear(); // just in case, #build() is not expected to be called more than once tikhomirov@596: do { tikhomirov@596: FileRevisionHistoryChunk fileHistory = new FileRevisionHistoryChunk(currentFile); tikhomirov@596: fileHistory.init(fileLastClogRevIndex); tikhomirov@596: fileHistory.linkTo(nextChunk); tikhomirov@596: fileCompleteHistory.addFirst(fileHistory); // to get the list in old-to-new order tikhomirov@596: nextChunk = fileHistory; tikhomirov@596: if (fileHistory.changeset(0) > changelogRevIndexStart && currentFile.isCopy()) { tikhomirov@596: // fileHistory.changeset(0) is the earliest revision we know about so far, tikhomirov@596: // once we get to revisions earlier than the requested start, stop digging. tikhomirov@596: // The reason there's NO == (i.e. not >=) because: tikhomirov@596: // (easy): once it's equal, we've reached our intended start tikhomirov@596: // (hard): if changelogRevIndexStart happens to be exact start of one of renames in the tikhomirov@596: // chain of renames (test-annotate2 repository, file1->file1a->file1b, i.e. points tikhomirov@596: // to the very start of file1a or file1 history), presence of == would get us to the next tikhomirov@596: // chunk and hence changed parents of present chunk's first element. Our annotate alg tikhomirov@596: // relies on parents only (i.e. knows nothing about 'last iteration element') to find out tikhomirov@596: // what to compare, and hence won't report all lines of 'last iteration element' (which is the tikhomirov@596: // first revision of the renamed file) as "added in this revision", leaving gaps in annotate tikhomirov@596: HgRepository repo = currentFile.getRepo(); tikhomirov@596: Nodeid originLastRev = currentFile.getCopySourceRevision(); tikhomirov@596: currentFile = repo.getFileNode(currentFile.getCopySourceName()); tikhomirov@596: fileLastClogRevIndex = currentFile.getChangesetRevisionIndex(currentFile.getRevisionIndex(originLastRev)); tikhomirov@596: // XXX perhaps, shall fail with meaningful exception if new file doesn't exist (.i/.d not found for whatever reason) tikhomirov@596: // or source revision is missing? tikhomirov@596: } else { tikhomirov@596: fileHistory.chopAtChangeset(changelogRevIndexStart); tikhomirov@596: currentFile = null; // stop iterating tikhomirov@596: } tikhomirov@596: } while (currentFile != null && fileLastClogRevIndex > changelogRevIndexStart); tikhomirov@596: // fileCompleteHistory is in (origin, intermediate target, ultimate target) order tikhomirov@596: } tikhomirov@596: tikhomirov@596: public Iterable iterate(HgIterateDirection order) { tikhomirov@596: if (order == NewToOld) { tikhomirov@596: return ReverseIterator.reversed(fileCompleteHistory); tikhomirov@596: } tikhomirov@596: return Collections.unmodifiableList(fileCompleteHistory); tikhomirov@596: } tikhomirov@596: }