comparison src/org/tmatesoft/hg/internal/FileRevisionHistoryChunk.java @ 691:72fc7774b87e

Fix file.isCopy() for blame/annotate. Refactor status and blame to use newly introduced FileHistory helper that builds file rename history
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 02 Aug 2013 23:07:23 +0200
parents 6526d8adbc0f
children
comparison
equal deleted inserted replaced
690:b286222158be 691:72fc7774b87e
23 import java.util.Arrays; 23 import java.util.Arrays;
24 import java.util.BitSet; 24 import java.util.BitSet;
25 import java.util.LinkedList; 25 import java.util.LinkedList;
26 26
27 import org.tmatesoft.hg.core.HgIterateDirection; 27 import org.tmatesoft.hg.core.HgIterateDirection;
28 import org.tmatesoft.hg.core.Nodeid;
29 import org.tmatesoft.hg.repo.HgDataFile; 28 import org.tmatesoft.hg.repo.HgDataFile;
30 import org.tmatesoft.hg.repo.HgRepository; 29 import org.tmatesoft.hg.repo.HgRepository;
31 import org.tmatesoft.hg.repo.HgRuntimeException; 30 import org.tmatesoft.hg.repo.HgRuntimeException;
32 31
33 /** 32 /**
39 */ 38 */
40 public final class FileRevisionHistoryChunk { 39 public final class FileRevisionHistoryChunk {
41 private final HgDataFile df; 40 private final HgDataFile df;
42 // change ancestry, sequence of file revisions 41 // change ancestry, sequence of file revisions
43 private IntVector fileRevsToVisit; 42 private IntVector fileRevsToVisit;
44 // parent pairs of complete file history 43 // parent pairs of complete file history, index offset by fileRevFrom
45 private IntVector fileParentRevs; 44 private IntVector fileParentRevs;
46 // map file revision to changelog revision (sparse array, only file revisions to visit are set) 45 // map file revision to changelog revision (sparse array, only file revisions to visit are set), index offset by fileRevFrom
47 private int[] file2changelog; 46 private int[] file2changelog;
48 private int originChangelogRev = BAD_REVISION, originFileRev = BAD_REVISION; 47 private int originChangelogRev = BAD_REVISION, originFileRev = BAD_REVISION;
49 private int csetRangeStart = NO_REVISION, csetRangeEnd = BAD_REVISION; 48 private final int csetFrom, csetTo;
49 private final int fileRevFrom, fileRevTo;
50 50
51 51
52 public FileRevisionHistoryChunk(HgDataFile file) { 52 public FileRevisionHistoryChunk(HgDataFile file, int csetStart, int csetEnd, int fileStart, int fileEnd) {
53 assert fileEnd >= fileStart;
53 df = file; 54 df = file;
55 csetFrom = csetStart;
56 csetTo = csetEnd;
57 fileRevFrom = fileStart;
58 fileRevTo = fileEnd;
54 } 59 }
55 60
56 /** 61 /**
57 * @return file at this specific chunk of history (i.e. its path may be different from the paths of other chunks) 62 * @return file at this specific chunk of history (i.e. its path may be different from the paths of other chunks)
58 */ 63 */
62 67
63 /** 68 /**
64 * @return changeset this file history chunk was chopped at, or {@link HgRepository#NO_REVISION} if none specified 69 * @return changeset this file history chunk was chopped at, or {@link HgRepository#NO_REVISION} if none specified
65 */ 70 */
66 public int getStartChangeset() { 71 public int getStartChangeset() {
67 return csetRangeStart; 72 return csetFrom;
68 } 73 }
69 74
70 /** 75 /**
71 * @return changeset this file history chunk ends at 76 * @return changeset this file history chunk ends at
72 */ 77 */
73 public int getEndChangeset() { 78 public int getEndChangeset() {
74 return csetRangeEnd; 79 return csetTo;
75 } 80 }
76 81
77 public void init(int changelogRevisionIndex) throws HgRuntimeException { 82 public void init() throws HgRuntimeException {
78 csetRangeEnd = changelogRevisionIndex;
79 // XXX df.indexWalk(0, fileRevIndex, ) might be more effective
80 Nodeid fileRev = df.getRepo().getManifest().getFileRevision(changelogRevisionIndex, df.getPath());
81 int fileRevIndex = df.getRevisionIndex(fileRev);
82 int[] fileRevParents = new int[2]; 83 int[] fileRevParents = new int[2];
83 fileParentRevs = new IntVector((fileRevIndex+1) * 2, 0); 84 final int totalFileRevs = fileRevTo - fileRevFrom + 1;
84 fileParentRevs.add(NO_REVISION, NO_REVISION); // parents of fileRevIndex == 0 85 fileParentRevs = new IntVector(totalFileRevs * 2, 0);
85 for (int i = 1; i <= fileRevIndex; i++) { 86 // pretend parents of fileRevStart are not set, regardless of actual state as we are not going to visit them anyway
87 fileParentRevs.add(NO_REVISION, NO_REVISION);
88 // XXX df.indexWalk(fileRevStart, fileRevEnd, ) might be more effective
89 for (int i = fileRevFrom+1; i <= fileRevTo; i++) {
86 df.parents(i, fileRevParents, null, null); 90 df.parents(i, fileRevParents, null, null);
87 fileParentRevs.add(fileRevParents[0], fileRevParents[1]); 91 fileParentRevs.add(fileRevParents[0], fileRevParents[1]);
88 } 92 }
89 // fileRevsToVisit keep file change ancestry from new to old 93 // fileRevsToVisit keep file change ancestry from new to old
90 fileRevsToVisit = new IntVector(fileRevIndex + 1, 0); 94 fileRevsToVisit = new IntVector(totalFileRevs, 0);
91 // keep map of file revision to changelog revision 95 // keep map of file revision to changelog revision
92 file2changelog = new int[fileRevIndex+1]; 96 file2changelog = new int[totalFileRevs];
93 // only elements worth visit would get mapped, so there would be unfilled areas in the file2changelog, 97 // only elements worth visit would get mapped, so there would be unfilled areas in the file2changelog,
94 // prevent from error (make it explicit) by bad value 98 // prevent from error (make it explicit) by bad value
95 Arrays.fill(file2changelog, BAD_REVISION); 99 Arrays.fill(file2changelog, BAD_REVISION);
96 LinkedList<Integer> queue = new LinkedList<Integer>(); 100 LinkedList<Integer> queue = new LinkedList<Integer>();
97 BitSet seen = new BitSet(fileRevIndex + 1); 101 BitSet seen = new BitSet(totalFileRevs);
98 queue.add(fileRevIndex); 102 queue.add(fileRevTo);
99 do { 103 do {
100 int x = queue.removeFirst(); 104 int fileRev = queue.removeFirst();
101 if (seen.get(x)) { 105 int offFileRev = fileRev - fileRevFrom;
106 if (seen.get(offFileRev)) {
102 continue; 107 continue;
103 } 108 }
104 seen.set(x); 109 seen.set(offFileRev);
105 fileRevsToVisit.add(x); 110 int csetRev = df.getChangesetRevisionIndex(fileRev);
106 file2changelog[x] = df.getChangesetRevisionIndex(x); 111 if (csetRev < csetFrom || csetRev > csetTo) {
107 int p1 = fileParentRevs.get(2*x); 112 continue;
108 int p2 = fileParentRevs.get(2*x + 1); 113 }
109 if (p1 != NO_REVISION) { 114 fileRevsToVisit.add(fileRev);
115
116 file2changelog[offFileRev] = csetRev;
117 int p1 = fileParentRevs.get(2*offFileRev);
118 int p2 = fileParentRevs.get(2*offFileRev + 1);
119 if (p1 != NO_REVISION && p1 >= fileRevFrom) {
110 queue.addLast(p1); 120 queue.addLast(p1);
111 } 121 }
112 if (p2 != NO_REVISION) { 122 if (p2 != NO_REVISION && p2 >= fileRevFrom) {
113 queue.addLast(p2); 123 queue.addLast(p2);
114 } 124 }
115 } while (!queue.isEmpty()); 125 } while (!queue.isEmpty());
116 // make sure no child is processed before we handled all (grand-)parents of the element 126 // make sure no child is processed before we handled all (grand-)parents of the element
117 fileRevsToVisit.sort(false); 127 fileRevsToVisit.sort(false);
118 } 128 }
119 129
120 public void linkTo(FileRevisionHistoryChunk target) { 130 public void linkTo(FileRevisionHistoryChunk next) {
121 // assume that target.init() has been called already 131 // assume that init() has been called already
122 if (target == null) { 132 if (next == null) {
123 return; 133 return;
124 } 134 }
125 target.originFileRev = fileRevsToVisit.get(0); // files to visit are new to old 135 next.originFileRev = fileRevsToVisit.get(0); // files to visit are new to old
126 target.originChangelogRev = changeset(target.originFileRev); 136 next.originChangelogRev = changeset(next.originFileRev);
127 }
128
129 /**
130 * Mark revision closest(ceil) to specified as the very first one (no parents)
131 */
132 public void chopAtChangeset(int firstChangelogRevOfInterest) {
133 csetRangeStart = firstChangelogRevOfInterest;
134 if (firstChangelogRevOfInterest == 0) {
135 return; // nothing to do
136 }
137 int i = 0, x = fileRevsToVisit.size(), fileRev = BAD_REVISION;
138 // fileRevsToVisit is new to old, greater numbers to smaller
139 while (i < x && changeset(fileRev = fileRevsToVisit.get(i)) >= firstChangelogRevOfInterest) {
140 i++;
141 }
142 assert fileRev != BAD_REVISION; // there's at least 1 revision in fileRevsToVisit
143 if (i == x && changeset(fileRev) != firstChangelogRevOfInterest) {
144 assert false : "Requested changeset shall belong to the chunk";
145 return;
146 }
147 fileRevsToVisit.trimTo(i); // no need to iterate more
148 // pretend fileRev got no parents
149 fileParentRevs.set(fileRev * 2, NO_REVISION);
150 fileParentRevs.set(fileRev, NO_REVISION);
151 } 137 }
152 138
153 public int[] fileRevisions(HgIterateDirection iterateOrder) { 139 public int[] fileRevisions(HgIterateDirection iterateOrder) {
154 // fileRevsToVisit is { r10, r7, r6, r5, r0 }, new to old 140 // fileRevsToVisit is { r10, r7, r6, r5, r0 }, new to old
155 int[] rv = fileRevsToVisit.toArray(); 141 int[] rv = fileRevsToVisit.toArray();
170 public int revisionCount() { 156 public int revisionCount() {
171 return fileRevsToVisit.size(); 157 return fileRevsToVisit.size();
172 } 158 }
173 159
174 public int changeset(int fileRevIndex) { 160 public int changeset(int fileRevIndex) {
175 return file2changelog[fileRevIndex]; 161 return file2changelog[fileRevIndex - fileRevFrom];
176 } 162 }
177 163
178 public void fillFileParents(int fileRevIndex, int[] fileParents) { 164 public void fillFileParents(int fileRevIndex, int[] fileParents) {
179 if (fileRevIndex == 0 && originFileRev != BAD_REVISION) { 165 if (fileRevIndex == fileRevFrom && originFileRev != BAD_REVISION) {
180 // this chunk continues another file 166 // this chunk continues another file
181 assert originFileRev != NO_REVISION; 167 assert originFileRev != NO_REVISION;
182 fileParents[0] = originFileRev; 168 fileParents[0] = originFileRev;
183 fileParents[1] = NO_REVISION; 169 fileParents[1] = NO_REVISION;
184 return; 170 return;
185 } 171 }
186 fileParents[0] = fileParentRevs.get(fileRevIndex * 2); 172 int x = fileRevIndex - fileRevFrom;
187 fileParents[1] = fileParentRevs.get(fileRevIndex * 2 + 1); 173 fileParents[0] = fileParentRevs.get(x * 2);
174 fileParents[1] = fileParentRevs.get(x * 2 + 1);
188 } 175 }
189 176
190 public void fillCsetParents(int fileRevIndex, int[] csetParents) { 177 public void fillCsetParents(int fileRevIndex, int[] csetParents) {
191 if (fileRevIndex == 0 && originFileRev != BAD_REVISION) { 178 if (fileRevIndex == fileRevFrom && originFileRev != BAD_REVISION) {
192 assert originFileRev != NO_REVISION; 179 assert originChangelogRev != NO_REVISION;
193 csetParents[0] = originChangelogRev; 180 csetParents[0] = originChangelogRev;
194 csetParents[1] = NO_REVISION; // I wonder if possible to start a copy with two parents? 181 csetParents[1] = NO_REVISION; // I wonder if possible to start a copy with two parents?
195 return; 182 return;
196 } 183 }
197 int fp1 = fileParentRevs.get(fileRevIndex * 2); 184 int x = fileRevIndex - fileRevFrom;
198 int fp2 = fileParentRevs.get(fileRevIndex * 2 + 1); 185 int fp1 = fileParentRevs.get(x * 2);
186 int fp2 = fileParentRevs.get(x * 2 + 1);
199 csetParents[0] = fp1 == NO_REVISION ? NO_REVISION : changeset(fp1); 187 csetParents[0] = fp1 == NO_REVISION ? NO_REVISION : changeset(fp1);
200 csetParents[1] = fp2 == NO_REVISION ? NO_REVISION : changeset(fp2); 188 csetParents[1] = fp2 == NO_REVISION ? NO_REVISION : changeset(fp2);
201 } 189 }
202 } 190 }