Mercurial > jhg
comparison src/org/tmatesoft/hg/repo/HgBlameFacility.java @ 569:c4fd1037bc6f
Support for copy/rename follow/no-follow for annotate
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Wed, 10 Apr 2013 20:04:54 +0200 |
| parents | 8ed4f4f4f0a6 |
| children | 36853bb80a35 |
comparison
equal
deleted
inserted
replaced
| 568:8ed4f4f4f0a6 | 569:c4fd1037bc6f |
|---|---|
| 14 * the terms of a license other than GNU General Public License | 14 * the terms of a license other than GNU General Public License |
| 15 * contact TMate Software at support@hg4j.com | 15 * contact TMate Software at support@hg4j.com |
| 16 */ | 16 */ |
| 17 package org.tmatesoft.hg.repo; | 17 package org.tmatesoft.hg.repo; |
| 18 | 18 |
| 19 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; | 19 import static org.tmatesoft.hg.core.HgIterateDirection.NewToOld; |
| 20 import static org.tmatesoft.hg.repo.HgRepository.TIP; | 20 import static org.tmatesoft.hg.core.HgIterateDirection.OldToNew; |
| 21 | 21 import static org.tmatesoft.hg.repo.HgInternals.wrongRevisionIndex; |
| 22 import static org.tmatesoft.hg.repo.HgRepository.*; | |
| 23 | |
| 24 import java.util.Arrays; | |
| 22 import java.util.BitSet; | 25 import java.util.BitSet; |
| 26 import java.util.Collections; | |
| 23 import java.util.LinkedList; | 27 import java.util.LinkedList; |
| 24 import java.util.ListIterator; | |
| 25 | 28 |
| 26 import org.tmatesoft.hg.core.HgCallbackTargetException; | 29 import org.tmatesoft.hg.core.HgCallbackTargetException; |
| 27 import org.tmatesoft.hg.core.HgIterateDirection; | 30 import org.tmatesoft.hg.core.HgIterateDirection; |
| 28 import org.tmatesoft.hg.core.Nodeid; | 31 import org.tmatesoft.hg.core.Nodeid; |
| 29 import org.tmatesoft.hg.internal.ByteArrayChannel; | 32 import org.tmatesoft.hg.internal.BlameHelper; |
| 30 import org.tmatesoft.hg.internal.Callback; | 33 import org.tmatesoft.hg.internal.Callback; |
| 31 import org.tmatesoft.hg.internal.DiffHelper; | |
| 32 import org.tmatesoft.hg.internal.Experimental; | 34 import org.tmatesoft.hg.internal.Experimental; |
| 33 import org.tmatesoft.hg.internal.IntMap; | |
| 34 import org.tmatesoft.hg.internal.IntVector; | 35 import org.tmatesoft.hg.internal.IntVector; |
| 35 import org.tmatesoft.hg.internal.Internals; | |
| 36 import org.tmatesoft.hg.internal.DiffHelper.LineSequence; | |
| 37 import org.tmatesoft.hg.internal.DiffHelper.LineSequence.ByteChain; | |
| 38 import org.tmatesoft.hg.internal.RangeSeq; | |
| 39 import org.tmatesoft.hg.repo.HgBlameFacility.RevisionDescriptor.Recipient; | |
| 40 import org.tmatesoft.hg.util.Adaptable; | 36 import org.tmatesoft.hg.util.Adaptable; |
| 41 import org.tmatesoft.hg.util.CancelledException; | |
| 42 import org.tmatesoft.hg.util.Pair; | |
| 43 | 37 |
| 44 /** | 38 /** |
| 45 * Facility with diff/annotate functionality. | 39 * Facility with diff/annotate functionality. |
| 46 * | 40 * |
| 47 * @author Artem Tikhomirov | 41 * @author Artem Tikhomirov |
| 60 | 54 |
| 61 /** | 55 /** |
| 62 * mimic 'hg diff -r clogRevIndex1 -r clogRevIndex2' | 56 * mimic 'hg diff -r clogRevIndex1 -r clogRevIndex2' |
| 63 */ | 57 */ |
| 64 public void diff(int clogRevIndex1, int clogRevIndex2, Inspector insp) throws HgCallbackTargetException { | 58 public void diff(int clogRevIndex1, int clogRevIndex2, Inspector insp) throws HgCallbackTargetException { |
| 59 // FIXME clogRevIndex1 and clogRevIndex2 may point to different files, need to decide whether to throw an exception | |
| 60 // or to attempt to look up correct file node (tricky) | |
| 65 int fileRevIndex1 = fileRevIndex(df, clogRevIndex1); | 61 int fileRevIndex1 = fileRevIndex(df, clogRevIndex1); |
| 66 int fileRevIndex2 = fileRevIndex(df, clogRevIndex2); | 62 int fileRevIndex2 = fileRevIndex(df, clogRevIndex2); |
| 67 FileLinesCache fileInfoCache = new FileLinesCache(df, 5); | 63 BlameHelper bh = new BlameHelper(insp, 5); |
| 68 LineSequence c1 = fileInfoCache.lines(fileRevIndex1); | 64 bh.useFileUpTo(df, clogRevIndex2); |
| 69 LineSequence c2 = fileInfoCache.lines(fileRevIndex2); | 65 bh.diff(fileRevIndex1, clogRevIndex1, fileRevIndex2, clogRevIndex2); |
| 70 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>(); | |
| 71 pg.init(c1, c2); | |
| 72 BlameBlockInspector bbi = new BlameBlockInspector(fileRevIndex2, insp, clogRevIndex1, clogRevIndex2); | |
| 73 pg.findMatchingBlocks(bbi); | |
| 74 bbi.checkErrors(); | |
| 75 } | 66 } |
| 76 | 67 |
| 77 /** | 68 /** |
| 78 * Walk file history up/down to revision at given changeset and report changes for each revision | 69 * Walk file history up/down to revision at given changeset and report changes for each revision |
| 79 */ | 70 */ |
| 80 public void annotate(int changelogRevisionIndex, Inspector insp, HgIterateDirection iterateOrder) throws HgCallbackTargetException { | 71 public void annotate(int changelogRevisionIndex, Inspector insp, HgIterateDirection iterateOrder) throws HgCallbackTargetException { |
| 72 annotate(0, changelogRevisionIndex, insp, iterateOrder); | |
| 73 } | |
| 74 | |
| 75 /** | |
| 76 * Walk file history range and report changes for each revision | |
| 77 */ | |
| 78 public void annotate(int changelogRevIndexStart, int changelogRevIndexEnd, Inspector insp, HgIterateDirection iterateOrder) throws HgCallbackTargetException { | |
| 79 if (wrongRevisionIndex(changelogRevIndexStart) || wrongRevisionIndex(changelogRevIndexEnd)) { | |
| 80 throw new IllegalArgumentException(); | |
| 81 } | |
| 82 // Note, changelogRevisionIndex may be TIP, while the code below doesn't tolerate constants | |
| 83 // | |
| 84 int lastRevision = df.getRepo().getChangelog().getLastRevision(); | |
| 85 if (changelogRevIndexEnd == TIP) { | |
| 86 changelogRevIndexEnd = lastRevision; | |
| 87 } | |
| 88 HgInternals.checkRevlogRange(changelogRevIndexStart, changelogRevIndexEnd, lastRevision); | |
| 81 if (!df.exists()) { | 89 if (!df.exists()) { |
| 82 return; | 90 return; |
| 83 } | 91 } |
| 84 // Note, changelogRevisionIndex may be TIP, while #implAnnotateChange doesn't tolerate constants | 92 BlameHelper bh = new BlameHelper(insp, 10); |
| 85 // | 93 HgDataFile currentFile = df; |
| 86 FileRevisionHistoryChunk fileHistory = new FileRevisionHistoryChunk(df); | 94 int fileLastClogRevIndex = changelogRevIndexEnd; |
| 87 fileHistory.init(changelogRevisionIndex); | 95 FileRevisionHistoryChunk nextChunk = null; |
| 88 // fileHistory.linkTo(null); FIXME | 96 LinkedList<FileRevisionHistoryChunk> fileCompleteHistory = new LinkedList<FileRevisionHistoryChunk>(); |
| 89 | 97 do { |
| 90 int[] fileRevParents = new int[2]; | 98 FileRevisionHistoryChunk fileHistory = new FileRevisionHistoryChunk(currentFile); |
| 91 FileLinesCache fileInfoCache = new FileLinesCache(df, 10); | 99 fileHistory.init(fileLastClogRevIndex); |
| 92 for (int fri : fileHistory.fileRevisions(iterateOrder)) { | 100 fileHistory.linkTo(nextChunk); |
| 93 int clogRevIndex = df.getChangesetRevisionIndex(fri); | 101 fileCompleteHistory.addFirst(fileHistory); // to get the list in old-to-new order |
| 94 fileHistory.getParents(fri, fileRevParents); | 102 nextChunk = fileHistory; |
| 95 implAnnotateChange(fileInfoCache, clogRevIndex, fri, fileRevParents, insp); | 103 bh.useFileUpTo(currentFile, fileLastClogRevIndex); |
| 104 if (currentFile.isCopy()) { | |
| 105 // TODO SessionContext.getPathFactory() and replace all Path.create | |
| 106 HgRepository repo = currentFile.getRepo(); | |
| 107 currentFile = repo.getFileNode(currentFile.getCopySourceName()); | |
| 108 fileLastClogRevIndex = repo.getChangelog().getRevisionIndex(currentFile.getCopySourceRevision()); | |
| 109 // XXX perhaps, shall fail with meaningful exception if new file doesn't exist (.i/.d not found for whatever reason) | |
| 110 // or source revision is missing? | |
| 111 } else { | |
| 112 currentFile = null; // stop iterating | |
| 113 } | |
| 114 } while (currentFile != null && fileLastClogRevIndex >= changelogRevIndexStart); | |
| 115 // fileCompleteHistory is in (origin, intermediate target, ultimate target) order | |
| 116 | |
| 117 int[] fileClogParentRevs = new int[2]; | |
| 118 int[] fileParentRevs = new int[2]; | |
| 119 if (iterateOrder == NewToOld) { | |
| 120 Collections.reverse(fileCompleteHistory); | |
| 121 } | |
| 122 boolean shallFilterStart = changelogRevIndexStart != 0; // no reason if complete history is walked | |
| 123 for (FileRevisionHistoryChunk fileHistory : fileCompleteHistory) { | |
| 124 for (int fri : fileHistory.fileRevisions(iterateOrder)) { | |
| 125 int clogRevIndex = fileHistory.changeset(fri); | |
| 126 if (shallFilterStart) { | |
| 127 if (iterateOrder == NewToOld) { | |
| 128 // clogRevIndex decreases | |
| 129 if (clogRevIndex < changelogRevIndexStart) { | |
| 130 break; | |
| 131 } | |
| 132 // fall-through, clogRevIndex is in the [start..end] range | |
| 133 } else { // old to new | |
| 134 // the way we built fileHistory ensures we won't walk past changelogRevIndexEnd | |
| 135 // here we ensure we start from the right one, the one indicated with changelogRevIndexStart | |
| 136 if (clogRevIndex < changelogRevIndexStart) { | |
| 137 continue; | |
| 138 } else { | |
| 139 shallFilterStart = false; // once boundary is crossed, no need to check | |
| 140 // fall-through | |
| 141 } | |
| 142 } | |
| 143 } | |
| 144 fileHistory.fillFileParents(fri, fileParentRevs); | |
| 145 fileHistory.fillCsetParents(fri, fileClogParentRevs); | |
| 146 bh.annotateChange(fri, clogRevIndex, fileParentRevs, fileClogParentRevs); | |
| 147 } | |
| 96 } | 148 } |
| 97 } | 149 } |
| 98 | 150 |
| 99 /** | 151 /** |
| 100 * Annotates changes of the file against its parent(s). | 152 * Annotates changes of the file against its parent(s). |
| 107 int[] fileRevParents = new int[2]; | 159 int[] fileRevParents = new int[2]; |
| 108 df.parents(fileRevIndex, fileRevParents, null, null); | 160 df.parents(fileRevIndex, fileRevParents, null, null); |
| 109 if (changelogRevisionIndex == TIP) { | 161 if (changelogRevisionIndex == TIP) { |
| 110 changelogRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex); | 162 changelogRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex); |
| 111 } | 163 } |
| 112 implAnnotateChange(new FileLinesCache(df, 5), changelogRevisionIndex, fileRevIndex, fileRevParents, insp); | 164 BlameHelper bh = new BlameHelper(insp, 5); |
| 113 } | 165 bh.useFileUpTo(df, changelogRevisionIndex); |
| 114 | 166 int[] fileClogParentRevs = new int[2]; |
| 115 private void implAnnotateChange(FileLinesCache fl, int csetRevIndex, int fileRevIndex, int[] fileParentRevs, Inspector insp) throws HgCallbackTargetException { | 167 fileClogParentRevs[0] = fileRevParents[0] == NO_REVISION ? NO_REVISION : df.getChangesetRevisionIndex(fileRevParents[0]); |
| 116 final LineSequence fileRevLines = fl.lines(fileRevIndex); | 168 fileClogParentRevs[1] = fileRevParents[1] == NO_REVISION ? NO_REVISION : df.getChangesetRevisionIndex(fileRevParents[1]); |
| 117 if (fileParentRevs[0] != NO_REVISION && fileParentRevs[1] != NO_REVISION) { | 169 bh.annotateChange(fileRevIndex, changelogRevisionIndex, fileRevParents, fileClogParentRevs); |
| 118 LineSequence p1Lines = fl.lines(fileParentRevs[0]); | |
| 119 LineSequence p2Lines = fl.lines(fileParentRevs[1]); | |
| 120 int p1ClogIndex = fl.getChangesetRevisionIndex(fileParentRevs[0]); | |
| 121 int p2ClogIndex = fl.getChangesetRevisionIndex(fileParentRevs[1]); | |
| 122 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>(); | |
| 123 pg.init(p2Lines, fileRevLines); | |
| 124 EqualBlocksCollector p2MergeCommon = new EqualBlocksCollector(); | |
| 125 pg.findMatchingBlocks(p2MergeCommon); | |
| 126 // | |
| 127 pg.init(p1Lines); | |
| 128 BlameBlockInspector bbi = new BlameBlockInspector(fileRevIndex, insp, p1ClogIndex, csetRevIndex); | |
| 129 bbi.setMergeParent2(p2MergeCommon, p2ClogIndex); | |
| 130 pg.findMatchingBlocks(bbi); | |
| 131 bbi.checkErrors(); | |
| 132 } else if (fileParentRevs[0] == fileParentRevs[1]) { | |
| 133 // may be equal iff both are unset | |
| 134 assert fileParentRevs[0] == NO_REVISION; | |
| 135 // everything added | |
| 136 BlameBlockInspector bbi = new BlameBlockInspector(fileRevIndex, insp, NO_REVISION, csetRevIndex); | |
| 137 bbi.begin(LineSequence.newlines(new byte[0]), fileRevLines); | |
| 138 bbi.match(0, fileRevLines.chunkCount()-1, 0); | |
| 139 bbi.end(); | |
| 140 bbi.checkErrors(); | |
| 141 } else { | |
| 142 int soleParent = fileParentRevs[0] == NO_REVISION ? fileParentRevs[1] : fileParentRevs[0]; | |
| 143 assert soleParent != NO_REVISION; | |
| 144 LineSequence parentLines = fl.lines(soleParent); | |
| 145 | |
| 146 int parentChangesetRevIndex = fl.getChangesetRevisionIndex(soleParent); | |
| 147 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>(); | |
| 148 pg.init(parentLines, fileRevLines); | |
| 149 BlameBlockInspector bbi = new BlameBlockInspector(fileRevIndex, insp, parentChangesetRevIndex, csetRevIndex); | |
| 150 pg.findMatchingBlocks(bbi); | |
| 151 bbi.checkErrors(); | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 private static int fileRevIndex(HgDataFile df, int csetRevIndex) { | |
| 156 Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath()); | |
| 157 return df.getRevisionIndex(fileRev); | |
| 158 } | |
| 159 | |
| 160 private static class FileRevisionHistoryChunk { | |
| 161 private final HgDataFile df; | |
| 162 private IntVector fileRevsToVisit; | |
| 163 private IntVector fileParentRevs; | |
| 164 | |
| 165 public FileRevisionHistoryChunk(HgDataFile file) { | |
| 166 df = file; | |
| 167 } | |
| 168 | |
| 169 public void getParents(int fileRevIndex, int[] fileRevParents) { | |
| 170 fileRevParents[0] = fileParentRevs.get(fileRevIndex * 2); | |
| 171 fileRevParents[1] = fileParentRevs.get(fileRevIndex * 2 + 1); | |
| 172 } | |
| 173 | |
| 174 public void init (int changelogRevisionIndex) { | |
| 175 // XXX df.indexWalk(0, fileRevIndex, ) might be more effective | |
| 176 int fileRevIndex = fileRevIndex(df, changelogRevisionIndex); | |
| 177 int[] fileRevParents = new int[2]; | |
| 178 fileParentRevs = new IntVector((fileRevIndex+1) * 2, 0); | |
| 179 fileParentRevs.add(NO_REVISION, NO_REVISION); // parents of fileRevIndex == 0 | |
| 180 for (int i = 1; i <= fileRevIndex; i++) { | |
| 181 df.parents(i, fileRevParents, null, null); | |
| 182 fileParentRevs.add(fileRevParents[0], fileRevParents[1]); | |
| 183 } | |
| 184 fileRevsToVisit = new IntVector(fileRevIndex + 1, 0); | |
| 185 LinkedList<Integer> queue = new LinkedList<Integer>(); | |
| 186 BitSet seen = new BitSet(fileRevIndex + 1); | |
| 187 queue.add(fileRevIndex); | |
| 188 do { | |
| 189 int x = queue.removeFirst(); | |
| 190 if (seen.get(x)) { | |
| 191 continue; | |
| 192 } | |
| 193 seen.set(x); | |
| 194 fileRevsToVisit.add(x); | |
| 195 int p1 = fileParentRevs.get(2*x); | |
| 196 int p2 = fileParentRevs.get(2*x + 1); | |
| 197 if (p1 != NO_REVISION) { | |
| 198 queue.addLast(p1); | |
| 199 } | |
| 200 if (p2 != NO_REVISION) { | |
| 201 queue.addLast(p2); | |
| 202 } | |
| 203 } while (!queue.isEmpty()); | |
| 204 // make sure no child is processed before we handled all (grand-)parents of the element | |
| 205 fileRevsToVisit.sort(false); | |
| 206 // now fileRevsToVisit keep file change ancestry from new to old | |
| 207 } | |
| 208 | |
| 209 public void linkTo(FileRevisionHistoryChunk origin) { | |
| 210 Internals.notImplemented(); | |
| 211 } | |
| 212 | |
| 213 public int[] fileRevisions(HgIterateDirection iterateOrder) { | |
| 214 // fileRevsToVisit is { r10, r7, r6, r5, r0 }, new to old | |
| 215 int[] rv = fileRevsToVisit.toArray(); | |
| 216 if (iterateOrder == HgIterateDirection.OldToNew) { | |
| 217 // reverse return value | |
| 218 for (int a = 0, b = rv.length-1; a < b; a++, b--) { | |
| 219 int t = rv[b]; | |
| 220 rv[b] = rv[a]; | |
| 221 rv[a] = t; | |
| 222 } | |
| 223 } | |
| 224 return rv; | |
| 225 } | |
| 226 } | |
| 227 | |
| 228 private static class FileLinesCache { | |
| 229 private final HgDataFile df; | |
| 230 private final LinkedList<Pair<Integer, LineSequence>> lruCache; | |
| 231 private final int limit; | |
| 232 private IntMap<Integer> fileToClogIndexMap = new IntMap<Integer>(20); | |
| 233 | |
| 234 public FileLinesCache(HgDataFile file, int lruLimit) { | |
| 235 df = file; | |
| 236 limit = lruLimit; | |
| 237 lruCache = new LinkedList<Pair<Integer, LineSequence>>(); | |
| 238 } | |
| 239 | |
| 240 public int getChangesetRevisionIndex(int fileRevIndex) { | |
| 241 Integer cached = fileToClogIndexMap.get(fileRevIndex); | |
| 242 if (cached == null) { | |
| 243 cached = df.getChangesetRevisionIndex(fileRevIndex); | |
| 244 fileToClogIndexMap.put(fileRevIndex, cached); | |
| 245 } | |
| 246 return cached.intValue(); | |
| 247 } | |
| 248 | |
| 249 public LineSequence lines(int fileRevIndex) { | |
| 250 Pair<Integer, LineSequence> cached = checkCache(fileRevIndex); | |
| 251 if (cached != null) { | |
| 252 return cached.second(); | |
| 253 } | |
| 254 try { | |
| 255 ByteArrayChannel c; | |
| 256 df.content(fileRevIndex, c = new ByteArrayChannel()); | |
| 257 LineSequence rv = LineSequence.newlines(c.toArray()); | |
| 258 lruCache.addFirst(new Pair<Integer, LineSequence>(fileRevIndex, rv)); | |
| 259 if (lruCache.size() > limit) { | |
| 260 lruCache.removeLast(); | |
| 261 } | |
| 262 return rv; | |
| 263 } catch (CancelledException ex) { | |
| 264 // TODO likely it was bad idea to throw cancelled exception from content() | |
| 265 // deprecate and provide alternative? | |
| 266 HgInvalidStateException ise = new HgInvalidStateException("ByteArrayChannel never throws CancelledException"); | |
| 267 ise.initCause(ex); | |
| 268 throw ise; | |
| 269 } | |
| 270 } | |
| 271 | |
| 272 private Pair<Integer,LineSequence> checkCache(int fileRevIndex) { | |
| 273 Pair<Integer, LineSequence> rv = null; | |
| 274 for (ListIterator<Pair<Integer, LineSequence>> it = lruCache.listIterator(); it.hasNext(); ) { | |
| 275 Pair<Integer, LineSequence> p = it.next(); | |
| 276 if (p.first() == fileRevIndex) { | |
| 277 rv = p; | |
| 278 it.remove(); | |
| 279 break; | |
| 280 } | |
| 281 } | |
| 282 if (rv != null) { | |
| 283 lruCache.addFirst(rv); | |
| 284 } | |
| 285 return rv; | |
| 286 } | |
| 287 } | 170 } |
| 288 | 171 |
| 289 /** | 172 /** |
| 290 * Client's sink for revision differences. | 173 * Client's sink for revision differences. |
| 291 * | 174 * |
| 371 * @return revision index of the change in target file's revlog | 254 * @return revision index of the change in target file's revlog |
| 372 */ | 255 */ |
| 373 int fileRevisionIndex(); | 256 int fileRevisionIndex(); |
| 374 | 257 |
| 375 /** | 258 /** |
| 259 * @return file object under blame (target file) | |
| 260 */ | |
| 261 HgDataFile file(); | |
| 262 | |
| 263 /** | |
| 376 * Implement to indicate interest in {@link RevisionDescriptor}. | 264 * Implement to indicate interest in {@link RevisionDescriptor}. |
| 377 * | 265 * |
| 378 * Note, instance of {@link RevisionDescriptor} is the same for | 266 * Note, instance of {@link RevisionDescriptor} is the same for |
| 379 * {@link #start(RevisionDescriptor)} and {@link #done(RevisionDescriptor)} | 267 * {@link #start(RevisionDescriptor)} and {@link #done(RevisionDescriptor)} |
| 380 * methods, and not necessarily a new one (i.e. <code>==</code>) for the next | 268 * methods, and not necessarily a new one (i.e. <code>==</code>) for the next |
| 445 */ | 333 */ |
| 446 BlockData removedLines(); | 334 BlockData removedLines(); |
| 447 } | 335 } |
| 448 public interface ChangeBlock extends AddBlock, DeleteBlock { | 336 public interface ChangeBlock extends AddBlock, DeleteBlock { |
| 449 } | 337 } |
| 450 | 338 |
| 451 private static class BlameBlockInspector extends DiffHelper.DeltaInspector<LineSequence> { | 339 |
| 452 private final Inspector insp; | 340 private static int fileRevIndex(HgDataFile df, int csetRevIndex) { |
| 453 private final int csetOrigin; | 341 Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath()); |
| 454 private final int csetTarget; | 342 return df.getRevisionIndex(fileRev); |
| 455 private EqualBlocksCollector p2MergeCommon; | 343 } |
| 456 private int csetMergeParent; | 344 |
| 457 private IntVector mergeRanges; | 345 private static class FileRevisionHistoryChunk { |
| 458 private final AnnotateRev annotatedRevision; | 346 private final HgDataFile df; |
| 459 private HgCallbackTargetException error; | 347 // change ancestry, sequence of file revisions |
| 460 | 348 private IntVector fileRevsToVisit; |
| 461 public BlameBlockInspector(int fileRevIndex, Inspector inspector, int originCset, int targetCset) { | 349 // parent pairs of complete file history |
| 462 assert inspector != null; | 350 private IntVector fileParentRevs; |
| 463 insp = inspector; | 351 // map file revision to changelog revision (sparse array, only file revisions to visit are set) |
| 464 annotatedRevision = new AnnotateRev(); | 352 private int[] file2changelog; |
| 465 annotatedRevision.set(fileRevIndex); | 353 private int originChangelogRev = BAD_REVISION, originFileRev = BAD_REVISION; |
| 466 csetOrigin = originCset; | 354 |
| 467 csetTarget = targetCset; | 355 public FileRevisionHistoryChunk(HgDataFile file) { |
| 468 } | 356 df = file; |
| 469 | 357 } |
| 470 public void setMergeParent2(EqualBlocksCollector p2Merge, int parentCset2) { | 358 |
| 471 p2MergeCommon = p2Merge; | 359 public void init(int changelogRevisionIndex) { |
| 472 csetMergeParent = parentCset2; | 360 // XXX df.indexWalk(0, fileRevIndex, ) might be more effective |
| 473 mergeRanges = new IntVector(3*10, 3*10); | 361 int fileRevIndex = fileRevIndex(df, changelogRevisionIndex); |
| 474 } | 362 int[] fileRevParents = new int[2]; |
| 475 | 363 fileParentRevs = new IntVector((fileRevIndex+1) * 2, 0); |
| 476 @Override | 364 fileParentRevs.add(NO_REVISION, NO_REVISION); // parents of fileRevIndex == 0 |
| 477 public void begin(LineSequence s1, LineSequence s2) { | 365 for (int i = 1; i <= fileRevIndex; i++) { |
| 478 super.begin(s1, s2); | 366 df.parents(i, fileRevParents, null, null); |
| 479 if (shallStop()) { | 367 fileParentRevs.add(fileRevParents[0], fileRevParents[1]); |
| 368 } | |
| 369 // fileRevsToVisit keep file change ancestry from new to old | |
| 370 fileRevsToVisit = new IntVector(fileRevIndex + 1, 0); | |
| 371 // keep map of file revision to changelog revision | |
| 372 file2changelog = new int[fileRevIndex+1]; | |
| 373 // only elements worth visit would get mapped, so there would be unfilled areas in the file2changelog, | |
| 374 // prevent from error (make it explicit) by bad value | |
| 375 Arrays.fill(file2changelog, BAD_REVISION); | |
| 376 LinkedList<Integer> queue = new LinkedList<Integer>(); | |
| 377 BitSet seen = new BitSet(fileRevIndex + 1); | |
| 378 queue.add(fileRevIndex); | |
| 379 do { | |
| 380 int x = queue.removeFirst(); | |
| 381 if (seen.get(x)) { | |
| 382 continue; | |
| 383 } | |
| 384 seen.set(x); | |
| 385 fileRevsToVisit.add(x); | |
| 386 file2changelog[x] = df.getChangesetRevisionIndex(x); | |
| 387 int p1 = fileParentRevs.get(2*x); | |
| 388 int p2 = fileParentRevs.get(2*x + 1); | |
| 389 if (p1 != NO_REVISION) { | |
| 390 queue.addLast(p1); | |
| 391 } | |
| 392 if (p2 != NO_REVISION) { | |
| 393 queue.addLast(p2); | |
| 394 } | |
| 395 } while (!queue.isEmpty()); | |
| 396 // make sure no child is processed before we handled all (grand-)parents of the element | |
| 397 fileRevsToVisit.sort(false); | |
| 398 } | |
| 399 | |
| 400 public void linkTo(FileRevisionHistoryChunk target) { | |
| 401 // assume that target.init() has been called already | |
| 402 if (target == null) { | |
| 480 return; | 403 return; |
| 481 } | 404 } |
| 482 ContentBlock originContent = new ContentBlock(s1); | 405 target.originFileRev = fileRevsToVisit.get(0); // files to visit are new to old |
| 483 ContentBlock targetContent = new ContentBlock(s2); | 406 target.originChangelogRev = changeset(target.originFileRev); |
| 484 annotatedRevision.set(originContent, targetContent); | 407 } |
| 485 annotatedRevision.set(csetOrigin, csetTarget, p2MergeCommon != null ? csetMergeParent : NO_REVISION); | 408 |
| 486 Recipient curious = Adaptable.Factory.getAdapter(insp, Recipient.class, null); | 409 public int[] fileRevisions(HgIterateDirection iterateOrder) { |
| 487 if (curious != null) { | 410 // fileRevsToVisit is { r10, r7, r6, r5, r0 }, new to old |
| 488 try { | 411 int[] rv = fileRevsToVisit.toArray(); |
| 489 curious.start(annotatedRevision); | 412 if (iterateOrder == OldToNew) { |
| 490 } catch (HgCallbackTargetException ex) { | 413 // reverse return value |
| 491 error = ex; | 414 for (int a = 0, b = rv.length-1; a < b; a++, b--) { |
| 415 int t = rv[b]; | |
| 416 rv[b] = rv[a]; | |
| 417 rv[a] = t; | |
| 492 } | 418 } |
| 493 } | 419 } |
| 494 } | 420 return rv; |
| 495 | 421 } |
| 496 @Override | 422 |
| 497 public void end() { | 423 public int changeset(int fileRevIndex) { |
| 498 super.end(); | 424 return file2changelog[fileRevIndex]; |
| 499 if (shallStop()) { | 425 } |
| 426 | |
| 427 public void fillFileParents(int fileRevIndex, int[] fileParents) { | |
| 428 if (fileRevIndex == 0 && originFileRev != BAD_REVISION) { | |
| 429 // this chunk continues another file | |
| 430 assert originFileRev != NO_REVISION; | |
| 431 fileParents[0] = originFileRev; | |
| 432 fileParents[1] = NO_REVISION; | |
| 500 return; | 433 return; |
| 501 } | 434 } |
| 502 Recipient curious = Adaptable.Factory.getAdapter(insp, Recipient.class, null); | 435 fileParents[0] = fileParentRevs.get(fileRevIndex * 2); |
| 503 if (curious != null) { | 436 fileParents[1] = fileParentRevs.get(fileRevIndex * 2 + 1); |
| 504 try { | 437 } |
| 505 curious.done(annotatedRevision); | 438 |
| 506 } catch (HgCallbackTargetException ex) { | 439 public void fillCsetParents(int fileRevIndex, int[] csetParents) { |
| 507 error = ex; | 440 if (fileRevIndex == 0 && originFileRev != BAD_REVISION) { |
| 508 } | 441 assert originFileRev != NO_REVISION; |
| 509 } | 442 csetParents[0] = originChangelogRev; |
| 510 p2MergeCommon = null; | 443 csetParents[1] = NO_REVISION; // I wonder if possible to start a copy with two parents? |
| 511 } | |
| 512 | |
| 513 @Override | |
| 514 protected void changed(int s1From, int s1To, int s2From, int s2To) { | |
| 515 if (shallStop()) { | |
| 516 return; | 444 return; |
| 517 } | 445 } |
| 518 try { | 446 int fp1 = fileParentRevs.get(fileRevIndex * 2); |
| 519 if (p2MergeCommon != null) { | 447 int fp2 = fileParentRevs.get(fileRevIndex * 2 + 1); |
| 520 mergeRanges.clear(); | 448 csetParents[0] = fp1 == NO_REVISION ? NO_REVISION : changeset(fp1); |
| 521 p2MergeCommon.combineAndMarkRangesWithTarget(s2From, s2To - s2From, csetOrigin, csetMergeParent, mergeRanges); | 449 csetParents[1] = fp2 == NO_REVISION ? NO_REVISION : changeset(fp2); |
| 522 | |
| 523 /* | |
| 524 * Usecases, how it USED TO BE initially: | |
| 525 * 3 lines changed to 10 lines. range of 10 lines breaks down to 2 from p2, 3 from p1, and 5 from p2. | |
| 526 * We report: 2 lines changed to 2(p2), then 1 line changed with 3(p1) and 5 lines added from p2. | |
| 527 * | |
| 528 * 10 lines changed to 3 lines, range of 3 lines breaks down to 2 line from p1 and 1 line from p2. | |
| 529 * We report: 2 lines changed to 2(p1) and 8 lines changed to 1(p2) | |
| 530 * | |
| 531 * NOW, lines from p2 are always reported as pure add (since we need their insertion point to be in p2, not in p1) | |
| 532 * and we try to consume p1 changes as soon as we see first p1's range | |
| 533 */ | |
| 534 int s1TotalLines = s1To - s1From, s1ConsumedLines = 0, s1Start = s1From; | |
| 535 | |
| 536 for (int i = 0; i < mergeRanges.size(); i += 3) { | |
| 537 final int rangeOrigin = mergeRanges.get(i); | |
| 538 final int rangeStart = mergeRanges.get(i+1); | |
| 539 final int rangeLen = mergeRanges.get(i+2); | |
| 540 final boolean lastRange = i+3 >= mergeRanges.size(); | |
| 541 final int s1LinesLeft = s1TotalLines - s1ConsumedLines; | |
| 542 // how many lines we may report as changed (don't use more than in range unless it's the very last range) | |
| 543 final int s1LinesToBorrow = lastRange ? s1LinesLeft : Math.min(s1LinesLeft, rangeLen); | |
| 544 if (rangeOrigin != csetMergeParent && s1LinesToBorrow > 0) { | |
| 545 ChangeBlockImpl block = getChangeBlock(s1Start, s1LinesToBorrow, rangeStart, rangeLen); | |
| 546 block.setOriginAndTarget(rangeOrigin, csetTarget); | |
| 547 insp.changed(block); | |
| 548 s1ConsumedLines += s1LinesToBorrow; | |
| 549 s1Start += s1LinesToBorrow; | |
| 550 } else { | |
| 551 int blockInsPoint = rangeOrigin != csetMergeParent ? s1Start : p2MergeCommon.reverseMapLine(rangeStart); | |
| 552 ChangeBlockImpl block = getAddBlock(rangeStart, rangeLen, blockInsPoint); | |
| 553 block.setOriginAndTarget(rangeOrigin, csetTarget); | |
| 554 insp.added(block); | |
| 555 } | |
| 556 } | |
| 557 if (s1ConsumedLines != s1TotalLines) { | |
| 558 assert s1ConsumedLines < s1TotalLines : String.format("Expected to process %d lines, but actually was %d", s1TotalLines, s1ConsumedLines); | |
| 559 // either there were no ranges from p1, whole s2From..s2To range came from p2, shall report as deleted | |
| 560 // or the ranges found were not enough to consume whole s2From..s2To | |
| 561 // The "deletion point" is shifted to the end of last csetOrigin->csetTarget change | |
| 562 int s2DeletePoint = s2From + s1ConsumedLines; | |
| 563 ChangeBlockImpl block = new ChangeBlockImpl(annotatedRevision.origin, null, s1Start, s1To - s1Start, -1, -1, -1, s2DeletePoint); | |
| 564 block.setOriginAndTarget(csetOrigin, csetTarget); | |
| 565 insp.deleted(block); | |
| 566 } | |
| 567 } else { | |
| 568 ChangeBlockImpl block = getChangeBlock(s1From, s1To - s1From, s2From, s2To - s2From); | |
| 569 block.setOriginAndTarget(csetOrigin, csetTarget); | |
| 570 insp.changed(block); | |
| 571 } | |
| 572 } catch (HgCallbackTargetException ex) { | |
| 573 error = ex; | |
| 574 } | |
| 575 } | |
| 576 | |
| 577 @Override | |
| 578 protected void added(int s1InsertPoint, int s2From, int s2To) { | |
| 579 if (shallStop()) { | |
| 580 return; | |
| 581 } | |
| 582 try { | |
| 583 if (p2MergeCommon != null) { | |
| 584 mergeRanges.clear(); | |
| 585 p2MergeCommon.combineAndMarkRangesWithTarget(s2From, s2To - s2From, csetOrigin, csetMergeParent, mergeRanges); | |
| 586 int insPoint = s1InsertPoint; // track changes to insertion point | |
| 587 for (int i = 0; i < mergeRanges.size(); i += 3) { | |
| 588 int rangeOrigin = mergeRanges.get(i); | |
| 589 int rangeStart = mergeRanges.get(i+1); | |
| 590 int rangeLen = mergeRanges.get(i+2); | |
| 591 ChangeBlockImpl block = getAddBlock(rangeStart, rangeLen, insPoint); | |
| 592 block.setOriginAndTarget(rangeOrigin, csetTarget); | |
| 593 insp.added(block); | |
| 594 // indicate insPoint moved down number of lines we just reported | |
| 595 insPoint += rangeLen; | |
| 596 } | |
| 597 } else { | |
| 598 ChangeBlockImpl block = getAddBlock(s2From, s2To - s2From, s1InsertPoint); | |
| 599 block.setOriginAndTarget(csetOrigin, csetTarget); | |
| 600 insp.added(block); | |
| 601 } | |
| 602 } catch (HgCallbackTargetException ex) { | |
| 603 error = ex; | |
| 604 } | |
| 605 } | |
| 606 | |
| 607 @Override | |
| 608 protected void deleted(int s2DeletePoint, int s1From, int s1To) { | |
| 609 if (shallStop()) { | |
| 610 return; | |
| 611 } | |
| 612 try { | |
| 613 ChangeBlockImpl block = new ChangeBlockImpl(annotatedRevision.origin, null, s1From, s1To - s1From, -1, -1, -1, s2DeletePoint); | |
| 614 block.setOriginAndTarget(csetOrigin, csetTarget); | |
| 615 insp.deleted(block); | |
| 616 } catch (HgCallbackTargetException ex) { | |
| 617 error = ex; | |
| 618 } | |
| 619 } | |
| 620 | |
| 621 @Override | |
| 622 protected void unchanged(int s1From, int s2From, int length) { | |
| 623 if (shallStop()) { | |
| 624 return; | |
| 625 } | |
| 626 try { | |
| 627 EqualBlockImpl block = new EqualBlockImpl(s1From, s2From, length, annotatedRevision.target); | |
| 628 block.setOriginAndTarget(csetOrigin, csetTarget); | |
| 629 insp.same(block); | |
| 630 } catch (HgCallbackTargetException ex) { | |
| 631 error = ex; | |
| 632 } | |
| 633 } | |
| 634 | |
| 635 void checkErrors() throws HgCallbackTargetException { | |
| 636 if (error != null) { | |
| 637 throw error; | |
| 638 } | |
| 639 } | |
| 640 | |
| 641 private boolean shallStop() { | |
| 642 return error != null; | |
| 643 } | |
| 644 | |
| 645 private ChangeBlockImpl getAddBlock(int start, int len, int insPoint) { | |
| 646 return new ChangeBlockImpl(null, annotatedRevision.target, -1, -1, start, len, insPoint, -1); | |
| 647 } | |
| 648 | |
| 649 private ChangeBlockImpl getChangeBlock(int start1, int len1, int start2, int len2) { | |
| 650 return new ChangeBlockImpl(annotatedRevision.origin, annotatedRevision.target, start1, len1, start2, len2, start1, start2); | |
| 651 } | |
| 652 } | |
| 653 | |
| 654 private static class BlockImpl implements Block { | |
| 655 private int originCset; | |
| 656 private int targetCset; | |
| 657 | |
| 658 void setOriginAndTarget(int originChangesetIndex, int targetChangesetIndex) { | |
| 659 // XXX perhaps, shall be part of Inspector API, rather than Block's | |
| 660 // as they don't change between blocks (although the moment about merged revisions) | |
| 661 // is not yet clear to me | |
| 662 originCset = originChangesetIndex; | |
| 663 targetCset = targetChangesetIndex; | |
| 664 } | |
| 665 | |
| 666 public int originChangesetIndex() { | |
| 667 return originCset; | |
| 668 } | |
| 669 | |
| 670 public int targetChangesetIndex() { | |
| 671 return targetCset; | |
| 672 } | |
| 673 } | |
| 674 | |
| 675 private static class EqualBlockImpl extends BlockImpl implements EqualBlock { | |
| 676 private final int start1, start2; | |
| 677 private final int length; | |
| 678 private final ContentBlock fullContent; | |
| 679 private FilterBlock myContent; | |
| 680 | |
| 681 EqualBlockImpl(int blockStartSeq1, int blockStartSeq2, int blockLength, ContentBlock targetContent) { | |
| 682 start1 = blockStartSeq1; | |
| 683 start2 = blockStartSeq2; | |
| 684 length = blockLength; | |
| 685 fullContent = targetContent; | |
| 686 } | |
| 687 | |
| 688 public int originStart() { | |
| 689 return start1; | |
| 690 } | |
| 691 | |
| 692 public int targetStart() { | |
| 693 return start2; | |
| 694 } | |
| 695 | |
| 696 public int length() { | |
| 697 return length; | |
| 698 } | |
| 699 | |
| 700 public BlockData content() { | |
| 701 if (myContent == null) { | |
| 702 myContent = new FilterBlock(fullContent, start2, length); | |
| 703 } | |
| 704 return myContent; | |
| 705 } | |
| 706 | |
| 707 @Override | |
| 708 public String toString() { | |
| 709 return String.format("@@ [%d..%d) == [%d..%d) @@", start1, start1+length, start2, start2+length); | |
| 710 } | |
| 711 } | |
| 712 | |
| 713 private static class ChangeBlockImpl extends BlockImpl implements ChangeBlock { | |
| 714 private final ContentBlock oldContent; | |
| 715 private final ContentBlock newContent; | |
| 716 private final int s1Start; | |
| 717 private final int s1Len; | |
| 718 private final int s2Start; | |
| 719 private final int s2Len; | |
| 720 private final int s1InsertPoint; | |
| 721 private final int s2DeletePoint; | |
| 722 private FilterBlock addedBlock, removedBlock; | |
| 723 | |
| 724 public ChangeBlockImpl(ContentBlock c1, ContentBlock c2, int s1Start, int s1Len, int s2Start, int s2Len, int s1InsertPoint, int s2DeletePoint) { | |
| 725 oldContent = c1; | |
| 726 newContent = c2; | |
| 727 this.s1Start = s1Start; | |
| 728 this.s1Len = s1Len; | |
| 729 this.s2Start = s2Start; | |
| 730 this.s2Len = s2Len; | |
| 731 this.s1InsertPoint = s1InsertPoint; | |
| 732 this.s2DeletePoint = s2DeletePoint; | |
| 733 } | |
| 734 | |
| 735 public int insertedAt() { | |
| 736 return s1InsertPoint; | |
| 737 } | |
| 738 | |
| 739 public int firstAddedLine() { | |
| 740 return s2Start; | |
| 741 } | |
| 742 | |
| 743 public int totalAddedLines() { | |
| 744 return s2Len; | |
| 745 } | |
| 746 | |
| 747 public BlockData addedLines() { | |
| 748 if (addedBlock == null) { | |
| 749 addedBlock = new FilterBlock(newContent, firstAddedLine(), totalAddedLines()); | |
| 750 } | |
| 751 return addedBlock; | |
| 752 } | |
| 753 | |
| 754 public int removedAt() { | |
| 755 return s2DeletePoint; | |
| 756 } | |
| 757 | |
| 758 public int firstRemovedLine() { | |
| 759 return s1Start; | |
| 760 } | |
| 761 | |
| 762 public int totalRemovedLines() { | |
| 763 return s1Len; | |
| 764 } | |
| 765 | |
| 766 public BlockData removedLines() { | |
| 767 if (removedBlock == null) { | |
| 768 removedBlock = new FilterBlock(oldContent, firstRemovedLine(), totalRemovedLines()); | |
| 769 } | |
| 770 return removedBlock; | |
| 771 } | |
| 772 | |
| 773 @Override | |
| 774 public String toString() { | |
| 775 if (s2DeletePoint == -1) { | |
| 776 return String.format("@@ -%d,0 +%d,%d @@", insertedAt(), firstAddedLine(), totalAddedLines()); | |
| 777 } else if (s1InsertPoint == -1) { | |
| 778 // delete only | |
| 779 return String.format("@@ -%d,%d +%d,0 @@", firstRemovedLine(), totalRemovedLines(), removedAt()); | |
| 780 } | |
| 781 return String.format("@@ -%d,%d +%d,%d @@", firstRemovedLine(), totalRemovedLines(), firstAddedLine(), totalAddedLines()); | |
| 782 } | |
| 783 } | |
| 784 | |
| 785 private static class SingleLine implements BlockData { | |
| 786 private final ByteChain line; | |
| 787 | |
| 788 public SingleLine(ByteChain lineContent) { | |
| 789 line = lineContent; | |
| 790 } | |
| 791 | |
| 792 public BlockData elementAt(int index) { | |
| 793 assert false; | |
| 794 return null; | |
| 795 } | |
| 796 | |
| 797 public int elementCount() { | |
| 798 return 0; | |
| 799 } | |
| 800 | |
| 801 public byte[] asArray() { | |
| 802 return line.data(); | |
| 803 } | |
| 804 } | |
| 805 | |
| 806 private static class ContentBlock implements BlockData { | |
| 807 private final LineSequence seq; | |
| 808 | |
| 809 public ContentBlock(LineSequence sequence) { | |
| 810 seq = sequence; | |
| 811 } | |
| 812 | |
| 813 public BlockData elementAt(int index) { | |
| 814 return new SingleLine(seq.chunk(index)); | |
| 815 } | |
| 816 | |
| 817 public int elementCount() { | |
| 818 return seq.chunkCount() - 1; | |
| 819 } | |
| 820 | |
| 821 public byte[] asArray() { | |
| 822 return seq.data(0, seq.chunkCount() - 1); | |
| 823 } | |
| 824 } | |
| 825 | |
| 826 private static class FilterBlock implements BlockData { | |
| 827 private final ContentBlock contentBlock; | |
| 828 private final int from; | |
| 829 private final int length; | |
| 830 | |
| 831 public FilterBlock(ContentBlock bd, int startFrom, int len) { | |
| 832 assert bd != null; | |
| 833 assert startFrom + len < bd.seq.chunkCount(); // there's one extra chunk in the end, so strict less is ok | |
| 834 contentBlock = bd; | |
| 835 from = startFrom; | |
| 836 length = len; | |
| 837 } | |
| 838 | |
| 839 public BlockData elementAt(int index) { | |
| 840 if (index < 0 || index >= length) { | |
| 841 throw new IllegalArgumentException(String.format("Expected value from [0..%d), got %d", length, index)); | |
| 842 } | |
| 843 return contentBlock.elementAt(from + index); | |
| 844 } | |
| 845 | |
| 846 public int elementCount() { | |
| 847 return length; | |
| 848 } | |
| 849 | |
| 850 public byte[] asArray() { | |
| 851 return contentBlock.seq.data(from, from + length); | |
| 852 } | |
| 853 } | |
| 854 | |
| 855 | |
| 856 private static class EqualBlocksCollector implements DiffHelper.MatchInspector<LineSequence> { | |
| 857 private final RangeSeq matches = new RangeSeq(); | |
| 858 | |
| 859 public void begin(LineSequence s1, LineSequence s2) { | |
| 860 } | |
| 861 | |
| 862 public void match(int startSeq1, int startSeq2, int matchLength) { | |
| 863 matches.add(startSeq1, startSeq2, matchLength); | |
| 864 } | |
| 865 | |
| 866 public void end() { | |
| 867 } | |
| 868 | |
| 869 public int reverseMapLine(int ln) { | |
| 870 return matches.reverseMapLine(ln); | |
| 871 } | |
| 872 | |
| 873 public void intersectWithTarget(int start, int length, IntVector result) { | |
| 874 int s = start; | |
| 875 for (int l = start, x = start + length; l < x; l++) { | |
| 876 if (!matches.includesTargetLine(l)) { | |
| 877 if (l - s > 0) { | |
| 878 result.add(s); | |
| 879 result.add(l - s); | |
| 880 } | |
| 881 s = l+1; | |
| 882 } | |
| 883 } | |
| 884 if (s < start+length) { | |
| 885 result.add(s); | |
| 886 result.add((start + length) - s); | |
| 887 } | |
| 888 } | |
| 889 | |
| 890 /* | |
| 891 * intersects [start..start+length) with ranges of target lines, and based on the intersection | |
| 892 * breaks initial range into smaller ranges and records them into result, with marker to indicate | |
| 893 * whether the range is from initial range (markerSource) or is a result of the intersection with target | |
| 894 * (markerTarget) | |
| 895 */ | |
| 896 public void combineAndMarkRangesWithTarget(int start, int length, int markerSource, int markerTarget, IntVector result) { | |
| 897 int sourceStart = start, targetStart = start, sourceEnd = start + length; | |
| 898 for (int l = sourceStart; l < sourceEnd; l++) { | |
| 899 if (matches.includesTargetLine(l)) { | |
| 900 // l is from target | |
| 901 if (sourceStart < l) { | |
| 902 // few lines from source range were not in the target, report them | |
| 903 result.add(markerSource); | |
| 904 result.add(sourceStart); | |
| 905 result.add(l - sourceStart); | |
| 906 } | |
| 907 // indicate the earliest line from source range to use | |
| 908 sourceStart = l + 1; | |
| 909 } else { | |
| 910 // l is not in target | |
| 911 if (targetStart < l) { | |
| 912 // report lines from target range | |
| 913 result.add(markerTarget); | |
| 914 result.add(targetStart); | |
| 915 result.add(l - targetStart); | |
| 916 } | |
| 917 // next line *may* be from target | |
| 918 targetStart = l + 1; | |
| 919 } | |
| 920 } | |
| 921 // if source range end with line from target, sourceStart would be == sourceEnd, and we need to add range with markerTarget | |
| 922 // if source range doesn't end with target line, targetStart == sourceEnd, while sourceStart < sourceEnd | |
| 923 if (sourceStart < sourceEnd) { | |
| 924 assert targetStart == sourceEnd; | |
| 925 // something left from the source range | |
| 926 result.add(markerSource); | |
| 927 result.add(sourceStart); | |
| 928 result.add(sourceEnd - sourceStart); | |
| 929 } else if (targetStart < sourceEnd) { | |
| 930 assert sourceStart == sourceEnd; | |
| 931 result.add(markerTarget); | |
| 932 result.add(targetStart); | |
| 933 result.add(sourceEnd - targetStart); | |
| 934 } | |
| 935 } | |
| 936 } | |
| 937 | |
| 938 private static class AnnotateRev implements RevisionDescriptor { | |
| 939 public ContentBlock origin, target; | |
| 940 public int originCset, targetCset, mergeCset, fileRevIndex; | |
| 941 | |
| 942 public void set(int fileRev) { | |
| 943 fileRevIndex = fileRev; | |
| 944 } | |
| 945 public void set(ContentBlock o, ContentBlock t) { | |
| 946 origin = o; | |
| 947 target = t; | |
| 948 } | |
| 949 public void set(int o, int t, int m) { | |
| 950 originCset = o; | |
| 951 targetCset = t; | |
| 952 mergeCset = m; | |
| 953 } | |
| 954 | |
| 955 public BlockData origin() { | |
| 956 return origin; | |
| 957 } | |
| 958 | |
| 959 public BlockData target() { | |
| 960 return target; | |
| 961 } | |
| 962 | |
| 963 public int originChangesetIndex() { | |
| 964 return originCset; | |
| 965 } | |
| 966 | |
| 967 public int targetChangesetIndex() { | |
| 968 return targetCset; | |
| 969 } | |
| 970 | |
| 971 public boolean isMerge() { | |
| 972 return mergeCset != NO_REVISION; | |
| 973 } | |
| 974 | |
| 975 public int mergeChangesetIndex() { | |
| 976 return mergeCset; | |
| 977 } | |
| 978 | |
| 979 public int fileRevisionIndex() { | |
| 980 return fileRevIndex; | |
| 981 } | |
| 982 @Override | |
| 983 public String toString() { | |
| 984 if (isMerge()) { | |
| 985 return String.format("[%d,%d->%d]", originCset, mergeCset, targetCset); | |
| 986 } | |
| 987 return String.format("[%d->%d]", originCset, targetCset); | |
| 988 } | |
| 989 } | |
| 990 | |
| 991 public static void main(String[] args) { | |
| 992 EqualBlocksCollector bc = new EqualBlocksCollector(); | |
| 993 bc.match(-1, 5, 3); | |
| 994 bc.match(-1, 10, 2); | |
| 995 bc.match(-1, 15, 3); | |
| 996 bc.match(-1, 20, 3); | |
| 997 IntVector r = new IntVector(); | |
| 998 bc.intersectWithTarget(7, 10, r); | |
| 999 for (int i = 0; i < r.size(); i+=2) { | |
| 1000 System.out.printf("[%d..%d) ", r.get(i), r.get(i) + r.get(i+1)); | |
| 1001 } | |
| 1002 System.out.println(); | |
| 1003 r.clear(); | |
| 1004 bc.combineAndMarkRangesWithTarget(0, 16, 508, 514, r); | |
| 1005 for (int i = 0; i < r.size(); i+=3) { | |
| 1006 System.out.printf("%d:[%d..%d) ", r.get(i), r.get(i+1), r.get(i+1) + r.get(i+2)); | |
| 1007 } | 450 } |
| 1008 } | 451 } |
| 1009 } | 452 } |
