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 }