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 } |