Mercurial > hg4j
comparison src/org/tmatesoft/hg/internal/FileRevisionHistoryChunk.java @ 596:43cfa08ff3fd
HgBlameFacility refactoring: extract code to build file history that spans renames
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Thu, 02 May 2013 19:23:53 +0200 |
| parents | |
| children | b4948b159ab1 |
comparison
equal
deleted
inserted
replaced
| 595:92c3ad9c2a51 | 596:43cfa08ff3fd |
|---|---|
| 1 /* | |
| 2 * Copyright (c) 2013 TMate Software Ltd | |
| 3 * | |
| 4 * This program is free software; you can redistribute it and/or modify | |
| 5 * it under the terms of the GNU General Public License as published by | |
| 6 * the Free Software Foundation; version 2 of the License. | |
| 7 * | |
| 8 * This program is distributed in the hope that it will be useful, | |
| 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 11 * GNU General Public License for more details. | |
| 12 * | |
| 13 * For information on how to redistribute this software under | |
| 14 * the terms of a license other than GNU General Public License | |
| 15 * contact TMate Software at support@hg4j.com | |
| 16 */ | |
| 17 package org.tmatesoft.hg.internal; | |
| 18 | |
| 19 import static org.tmatesoft.hg.core.HgIterateDirection.OldToNew; | |
| 20 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; | |
| 21 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; | |
| 22 | |
| 23 import java.util.Arrays; | |
| 24 import java.util.BitSet; | |
| 25 import java.util.LinkedList; | |
| 26 | |
| 27 import org.tmatesoft.hg.core.HgIterateDirection; | |
| 28 import org.tmatesoft.hg.core.Nodeid; | |
| 29 import org.tmatesoft.hg.repo.HgDataFile; | |
| 30 import org.tmatesoft.hg.repo.HgRepository; | |
| 31 | |
| 32 /** | |
| 33 * Piece of file history, identified by path, limited to file revisions from range [chop..init] of changesets, | |
| 34 * can be linked to another piece. | |
| 35 * | |
| 36 * @author Artem Tikhomirov | |
| 37 * @author TMate Software Ltd. | |
| 38 */ | |
| 39 public final class FileRevisionHistoryChunk { | |
| 40 private final HgDataFile df; | |
| 41 // change ancestry, sequence of file revisions | |
| 42 private IntVector fileRevsToVisit; | |
| 43 // parent pairs of complete file history | |
| 44 private IntVector fileParentRevs; | |
| 45 // map file revision to changelog revision (sparse array, only file revisions to visit are set) | |
| 46 private int[] file2changelog; | |
| 47 private int originChangelogRev = BAD_REVISION, originFileRev = BAD_REVISION; | |
| 48 private int csetRangeStart = NO_REVISION, csetRangeEnd = BAD_REVISION; | |
| 49 | |
| 50 | |
| 51 public FileRevisionHistoryChunk(HgDataFile file) { | |
| 52 df = file; | |
| 53 } | |
| 54 | |
| 55 /** | |
| 56 * @return file at this specific chunk of history (i.e. its path may be different from the paths of other chunks) | |
| 57 */ | |
| 58 public HgDataFile getFile() { | |
| 59 return df; | |
| 60 } | |
| 61 | |
| 62 /** | |
| 63 * @return changeset this file history chunk was chopped at, or {@link HgRepository#NO_REVISION} if none specified | |
| 64 */ | |
| 65 public int getStartChangeset() { | |
| 66 return csetRangeStart; | |
| 67 } | |
| 68 | |
| 69 /** | |
| 70 * @return changeset this file history chunk ends at | |
| 71 */ | |
| 72 public int getEndChangeset() { | |
| 73 return csetRangeEnd; | |
| 74 } | |
| 75 | |
| 76 public void init(int changelogRevisionIndex) { | |
| 77 csetRangeEnd = changelogRevisionIndex; | |
| 78 // XXX df.indexWalk(0, fileRevIndex, ) might be more effective | |
| 79 Nodeid fileRev = df.getRepo().getManifest().getFileRevision(changelogRevisionIndex, df.getPath()); | |
| 80 int fileRevIndex = df.getRevisionIndex(fileRev); | |
| 81 int[] fileRevParents = new int[2]; | |
| 82 fileParentRevs = new IntVector((fileRevIndex+1) * 2, 0); | |
| 83 fileParentRevs.add(NO_REVISION, NO_REVISION); // parents of fileRevIndex == 0 | |
| 84 for (int i = 1; i <= fileRevIndex; i++) { | |
| 85 df.parents(i, fileRevParents, null, null); | |
| 86 fileParentRevs.add(fileRevParents[0], fileRevParents[1]); | |
| 87 } | |
| 88 // fileRevsToVisit keep file change ancestry from new to old | |
| 89 fileRevsToVisit = new IntVector(fileRevIndex + 1, 0); | |
| 90 // keep map of file revision to changelog revision | |
| 91 file2changelog = new int[fileRevIndex+1]; | |
| 92 // only elements worth visit would get mapped, so there would be unfilled areas in the file2changelog, | |
| 93 // prevent from error (make it explicit) by bad value | |
| 94 Arrays.fill(file2changelog, BAD_REVISION); | |
| 95 LinkedList<Integer> queue = new LinkedList<Integer>(); | |
| 96 BitSet seen = new BitSet(fileRevIndex + 1); | |
| 97 queue.add(fileRevIndex); | |
| 98 do { | |
| 99 int x = queue.removeFirst(); | |
| 100 if (seen.get(x)) { | |
| 101 continue; | |
| 102 } | |
| 103 seen.set(x); | |
| 104 fileRevsToVisit.add(x); | |
| 105 file2changelog[x] = df.getChangesetRevisionIndex(x); | |
| 106 int p1 = fileParentRevs.get(2*x); | |
| 107 int p2 = fileParentRevs.get(2*x + 1); | |
| 108 if (p1 != NO_REVISION) { | |
| 109 queue.addLast(p1); | |
| 110 } | |
| 111 if (p2 != NO_REVISION) { | |
| 112 queue.addLast(p2); | |
| 113 } | |
| 114 } while (!queue.isEmpty()); | |
| 115 // make sure no child is processed before we handled all (grand-)parents of the element | |
| 116 fileRevsToVisit.sort(false); | |
| 117 } | |
| 118 | |
| 119 public void linkTo(FileRevisionHistoryChunk target) { | |
| 120 // assume that target.init() has been called already | |
| 121 if (target == null) { | |
| 122 return; | |
| 123 } | |
| 124 target.originFileRev = fileRevsToVisit.get(0); // files to visit are new to old | |
| 125 target.originChangelogRev = changeset(target.originFileRev); | |
| 126 } | |
| 127 | |
| 128 /** | |
| 129 * Mark revision closest(ceil) to specified as the very first one (no parents) | |
| 130 */ | |
| 131 public void chopAtChangeset(int firstChangelogRevOfInterest) { | |
| 132 csetRangeStart = firstChangelogRevOfInterest; | |
| 133 if (firstChangelogRevOfInterest == 0) { | |
| 134 return; // nothing to do | |
| 135 } | |
| 136 int i = 0, x = fileRevsToVisit.size(), fileRev = BAD_REVISION; | |
| 137 // fileRevsToVisit is new to old, greater numbers to smaller | |
| 138 while (i < x && changeset(fileRev = fileRevsToVisit.get(i)) >= firstChangelogRevOfInterest) { | |
| 139 i++; | |
| 140 } | |
| 141 assert fileRev != BAD_REVISION; // there's at least 1 revision in fileRevsToVisit | |
| 142 if (i == x && changeset(fileRev) != firstChangelogRevOfInterest) { | |
| 143 assert false : "Requested changeset shall belong to the chunk"; | |
| 144 return; | |
| 145 } | |
| 146 fileRevsToVisit.trimTo(i); // no need to iterate more | |
| 147 // pretend fileRev got no parents | |
| 148 fileParentRevs.set(fileRev * 2, NO_REVISION); | |
| 149 fileParentRevs.set(fileRev, NO_REVISION); | |
| 150 } | |
| 151 | |
| 152 public int[] fileRevisions(HgIterateDirection iterateOrder) { | |
| 153 // fileRevsToVisit is { r10, r7, r6, r5, r0 }, new to old | |
| 154 int[] rv = fileRevsToVisit.toArray(); | |
| 155 if (iterateOrder == OldToNew) { | |
| 156 // reverse return value | |
| 157 for (int a = 0, b = rv.length-1; a < b; a++, b--) { | |
| 158 int t = rv[b]; | |
| 159 rv[b] = rv[a]; | |
| 160 rv[a] = t; | |
| 161 } | |
| 162 } | |
| 163 return rv; | |
| 164 } | |
| 165 | |
| 166 public int changeset(int fileRevIndex) { | |
| 167 return file2changelog[fileRevIndex]; | |
| 168 } | |
| 169 | |
| 170 public void fillFileParents(int fileRevIndex, int[] fileParents) { | |
| 171 if (fileRevIndex == 0 && originFileRev != BAD_REVISION) { | |
| 172 // this chunk continues another file | |
| 173 assert originFileRev != NO_REVISION; | |
| 174 fileParents[0] = originFileRev; | |
| 175 fileParents[1] = NO_REVISION; | |
| 176 return; | |
| 177 } | |
| 178 fileParents[0] = fileParentRevs.get(fileRevIndex * 2); | |
| 179 fileParents[1] = fileParentRevs.get(fileRevIndex * 2 + 1); | |
| 180 } | |
| 181 | |
| 182 public void fillCsetParents(int fileRevIndex, int[] csetParents) { | |
| 183 if (fileRevIndex == 0 && originFileRev != BAD_REVISION) { | |
| 184 assert originFileRev != NO_REVISION; | |
| 185 csetParents[0] = originChangelogRev; | |
| 186 csetParents[1] = NO_REVISION; // I wonder if possible to start a copy with two parents? | |
| 187 return; | |
| 188 } | |
| 189 int fp1 = fileParentRevs.get(fileRevIndex * 2); | |
| 190 int fp2 = fileParentRevs.get(fileRevIndex * 2 + 1); | |
| 191 csetParents[0] = fp1 == NO_REVISION ? NO_REVISION : changeset(fp1); | |
| 192 csetParents[1] = fp2 == NO_REVISION ? NO_REVISION : changeset(fp2); | |
| 193 } | |
| 194 } |
