comparison src/org/tmatesoft/hg/repo/HgManifest.java @ 390:6952d9ce97f1

Handle missing manifest revision case (brought up with Issue 23), do my best to report missing manifests when walking few manifest revisions
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 16 Feb 2012 16:08:51 +0100
parents 6150555eb41d
children 2747b0723867 63c5a9d7ca3f
comparison
equal deleted inserted replaced
389:82bec80bb1a4 390:6952d9ce97f1
1 /* 1 /*
2 * Copyright (c) 2010-2011 TMate Software Ltd 2 * Copyright (c) 2010-2012 TMate Software Ltd
3 * 3 *
4 * This program is free software; you can redistribute it and/or modify 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 5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License. 6 * the Free Software Foundation; version 2 of the License.
7 * 7 *
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.core.Nodeid.NULL;
20 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
19 import static org.tmatesoft.hg.repo.HgRepository.TIP; 21 import static org.tmatesoft.hg.repo.HgRepository.TIP;
20 22
21 import java.io.ByteArrayOutputStream; 23 import java.io.ByteArrayOutputStream;
22 import java.io.IOException; 24 import java.io.IOException;
23 import java.util.ArrayList; 25 import java.util.ArrayList;
24 import java.util.Arrays; 26 import java.util.Arrays;
25 import java.util.HashMap; 27 import java.util.HashMap;
26 import java.util.Map; 28 import java.util.Map;
27 29
30 import org.tmatesoft.hg.core.HgBadStateException;
28 import org.tmatesoft.hg.core.HgException; 31 import org.tmatesoft.hg.core.HgException;
29 import org.tmatesoft.hg.core.HgInvalidControlFileException; 32 import org.tmatesoft.hg.core.HgInvalidControlFileException;
30 import org.tmatesoft.hg.core.Nodeid; 33 import org.tmatesoft.hg.core.Nodeid;
31 import org.tmatesoft.hg.internal.DataAccess; 34 import org.tmatesoft.hg.internal.DataAccess;
32 import org.tmatesoft.hg.internal.DigestHelper; 35 import org.tmatesoft.hg.internal.DigestHelper;
95 /*package-local*/ HgManifest(HgRepository hgRepo, RevlogStream content) { 98 /*package-local*/ HgManifest(HgRepository hgRepo, RevlogStream content) {
96 super(hgRepo, content); 99 super(hgRepo, content);
97 } 100 }
98 101
99 /** 102 /**
103 * Walks manifest revisions that correspond to specified range of changesets. The order in which manifest versions get reported
104 * to the inspector corresponds to physical order of manifest revisions, not that of changesets (with few exceptions as noted below).
105 * That is, for cset-manifest revision pairs:
106 * <pre>
107 * 3 8
108 * 4 7
109 * 5 9
110 * </pre>
111 * call <code>walk(3,5, insp)</code> would yield (4,7), (3,8) and (5,9) to the inspector;
112 * different order of arguments, <code>walk(5, 3, insp)</code>, makes no difference.
113 *
114 * <p>Physical layout of mercurial files (revlog) doesn't impose any restriction on whether manifest and changeset revisions shall go
115 * incrementally, nor it mandates presence of manifest version for a changeset. Thus, there might be changesets that record {@link Nodeid#NULL}
116 * as corresponding manifest revision. This situation is deemed exceptional now and what would <code>inspector</code> get depends on whether
117 * <code>start</code> or <code>end</code> arguments point to such changeset, or such changeset happen to be somewhere inside the range
118 * <code>[start..end]</code>. Implementation does it best to report empty manifests (<code>Inspector.begin(BAD_REVISION, NULL, csetRevIndex);</code>
119 * followed immediately by <code>Inspector.end(BAD_REVISION)</code> when <code>start</code> and/or <code>end</code> point to changeset with no associated
120 * manifest revision. However, if changeset-manifest revision pairs look like:
121 * <pre>
122 * 3 8
123 * 4 -1 (cset records null revision for manifest)
124 * 5 9
125 * </pre>
126 * call <code>walk(3,5, insp)</code> would yield only (3,8) and (5,9) to the inspector, without additional empty
127 * <code>Inspector.begin(); Inspector.end()</code> call pair.
100 * 128 *
101 * @param start changelog (not manifest!) revision to begin with 129 * @param start changelog (not manifest!) revision to begin with
102 * @param end changelog (not manifest!) revision to stop, inclusive. 130 * @param end changelog (not manifest!) revision to stop, inclusive.
103 * @param inspector can't be <code>null</code> 131 * @param inspector manifest revision visitor, can't be <code>null</code>
132 * @throws HgInvalidControlFileException if access to revlog index/data entry failed
104 */ 133 */
105 public void walk(int start, int end, final Inspector inspector) throws /*FIXME HgInvalidRevisionException,*/ HgInvalidControlFileException { 134 public void walk(int start, int end, final Inspector inspector) throws /*FIXME HgInvalidRevisionException,*/ HgInvalidControlFileException {
106 if (inspector == null) { 135 if (inspector == null) {
107 throw new IllegalArgumentException(); 136 throw new IllegalArgumentException();
108 } 137 }
109 int start0 = fromChangelog(start); 138 final int csetFirst = start <= end ? start : end, csetLast = start > end ? start : end;
110 int end0 = fromChangelog(end); 139 int manifestFirst, manifestLast, i = 0;
111 if (end0 < start0) { 140 do {
141 manifestFirst = fromChangelog(csetFirst+i);
142 if (manifestFirst == -1) {
143 inspector.begin(BAD_REVISION, NULL, csetFirst+i);
144 inspector.end(BAD_REVISION);
145 }
146 i++;
147 } while (manifestFirst == -1 && csetFirst+i <= csetLast);
148 if (manifestFirst == -1) {
149 getRepo().getContext().getLog().info(getClass(), "None of changesets [%d..%d] have associated manifest revision", csetFirst, csetLast);
150 // we ran through all revisions in [start..end] and none of them had manifest.
151 // we reported that to inspector and proceeding is done now.
152 return;
153 }
154 i = 0;
155 do {
156 manifestLast = fromChangelog(csetLast-i);
157 if (manifestLast == -1) {
158 inspector.begin(BAD_REVISION, NULL, csetLast-i);
159 inspector.end(BAD_REVISION);
160 }
161 i++;
162 } while (manifestLast == -1 && csetLast-i >= csetFirst);
163 if (manifestLast == -1) {
164 // hmm, manifestFirst != -1 here, hence there's i from [csetFirst..csetLast] for which manifest entry exists,
165 // and thus it's impossible to run into manifestLast == -1. Nevertheless, never hurts to check.
166 throw new HgBadStateException(String.format("Manifest %d-%d(!) for cset range [%d..%d] ", manifestFirst, manifestLast, csetFirst, csetLast));
167 }
168 if (manifestLast < manifestFirst) {
112 // there are tool-constructed repositories that got order of changeset revisions completely different from that of manifest 169 // there are tool-constructed repositories that got order of changeset revisions completely different from that of manifest
113 int x = end0; 170 int x = manifestLast;
114 end0 = start0; 171 manifestLast = manifestFirst;
115 start0 = x; 172 manifestFirst = x;
116 } 173 }
117 content.iterate(start0, end0, true, new ManifestParser(inspector)); 174 content.iterate(manifestFirst, manifestLast, true, new ManifestParser(inspector));
118 } 175 }
119 176
120 /** 177 /**
121 * "Sparse" iteration of the manifest 178 * "Sparse" iteration of the manifest, more effective than accessing revisions one by one.
179 * <p> Inspector is invoked for each changeset revision supplied, even when there's no manifest
180 * revision associated with a changeset (@see {@link #walk(int, int, Inspector)} for more details when it happens). Order inspector
181 * gets invoked doesn't resemble order of changeset revisions supplied, manifest revisions are reported in the order they appear
182 * in manifest revlog (with exception of changesets with missing manifest that may be reported in any order).
122 * 183 *
123 * @param inspector 184 * @param inspector manifest revision visitor, can't be <code>null</code>
124 * @param revisionIndexes local indexes of changesets to visit 185 * @param revisionIndexes local indexes of changesets to visit, non-<code>null</code>
125 */ 186 */
126 public void walk(final Inspector inspector, int... revisionIndexes) throws HgInvalidControlFileException{ 187 public void walk(final Inspector inspector, int... revisionIndexes) throws HgInvalidControlFileException{
127 if (inspector == null || revisionIndexes == null) { 188 if (inspector == null || revisionIndexes == null) {
128 throw new IllegalArgumentException(); 189 throw new IllegalArgumentException();
129 } 190 }
130 int[] manifestRevs = toManifestRevisionIndexes(revisionIndexes); 191 int[] manifestRevs = toManifestRevisionIndexes(revisionIndexes, inspector);
131 content.iterate(manifestRevs, true, new ManifestParser(inspector)); 192 content.iterate(manifestRevs, true, new ManifestParser(inspector));
132 } 193 }
133 194
134 // manifest revision number that corresponds to the given changeset 195 //
196 /**
197 * Tells manifest revision number that corresponds to the given changeset.
198 * @return manifest revision index, or -1 if changeset has no associated manifest (cset records NULL nodeid for manifest)
199 */
135 /*package-local*/ int fromChangelog(int changesetRevisionIndex) throws HgInvalidControlFileException { 200 /*package-local*/ int fromChangelog(int changesetRevisionIndex) throws HgInvalidControlFileException {
136 if (HgInternals.wrongRevisionIndex(changesetRevisionIndex)) { 201 if (HgInternals.wrongRevisionIndex(changesetRevisionIndex)) {
137 throw new IllegalArgumentException(String.valueOf(changesetRevisionIndex)); 202 throw new IllegalArgumentException(String.valueOf(changesetRevisionIndex));
138 } 203 }
139 if (changesetRevisionIndex == HgRepository.WORKING_COPY || changesetRevisionIndex == HgRepository.BAD_REVISION) { 204 if (changesetRevisionIndex == HgRepository.WORKING_COPY || changesetRevisionIndex == HgRepository.BAD_REVISION) {
161 226
162 // XXX package-local, IntMap, and HgDataFile getFileRevisionAt(int... localChangelogRevisions) 227 // XXX package-local, IntMap, and HgDataFile getFileRevisionAt(int... localChangelogRevisions)
163 @Experimental(reason="@see #getFileRevision") 228 @Experimental(reason="@see #getFileRevision")
164 public Map<Integer, Nodeid> getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidControlFileException{ 229 public Map<Integer, Nodeid> getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidControlFileException{
165 // FIXME need tests 230 // FIXME need tests
166 int[] manifestRevisionIndexes = toManifestRevisionIndexes(changelogRevisionIndexes); 231 int[] manifestRevisionIndexes = toManifestRevisionIndexes(changelogRevisionIndexes, null);
167 final HashMap<Integer,Nodeid> rv = new HashMap<Integer, Nodeid>(changelogRevisionIndexes.length); 232 final HashMap<Integer,Nodeid> rv = new HashMap<Integer, Nodeid>(changelogRevisionIndexes.length);
168 content.iterate(manifestRevisionIndexes, true, new RevlogStream.Inspector() { 233 content.iterate(manifestRevisionIndexes, true, new RevlogStream.Inspector() {
169 234
170 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgException { 235 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) throws HgException {
171 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 236 ByteArrayOutputStream bos = new ByteArrayOutputStream();
197 }); 262 });
198 return rv; 263 return rv;
199 } 264 }
200 265
201 266
202 private int[] toManifestRevisionIndexes(int[] changelogRevisionIndexes) throws HgInvalidControlFileException { 267 /**
268 * @param changelogRevisionIndexes non-null
269 * @param inspector may be null if reporting of missing manifests is not needed
270 */
271 private int[] toManifestRevisionIndexes(int[] changelogRevisionIndexes, Inspector inspector) throws HgInvalidControlFileException {
203 int[] manifestRevs = new int[changelogRevisionIndexes.length]; 272 int[] manifestRevs = new int[changelogRevisionIndexes.length];
204 boolean needsSort = false; 273 boolean needsSort = false;
274 int j = 0;
205 for (int i = 0; i < changelogRevisionIndexes.length; i++) { 275 for (int i = 0; i < changelogRevisionIndexes.length; i++) {
206 final int manifestRevisionIndex = fromChangelog(changelogRevisionIndexes[i]); 276 final int manifestRevisionIndex = fromChangelog(changelogRevisionIndexes[i]);
207 manifestRevs[i] = manifestRevisionIndex; 277 if (manifestRevisionIndex == -1) {
208 if (i > 0 && manifestRevs[i-1] > manifestRevisionIndex) { 278 if (inspector != null) {
209 needsSort = true; 279 inspector.begin(BAD_REVISION, NULL, changelogRevisionIndexes[i]);
280 inspector.end(BAD_REVISION);
281 }
282 // othrwise, ignore changeset without manifest
283 } else {
284 manifestRevs[j] = manifestRevisionIndex;
285 if (j > 0 && manifestRevs[j-1] > manifestRevisionIndex) {
286 needsSort = true;
287 }
288 j++;
210 } 289 }
211 } 290 }
212 if (needsSort) { 291 if (needsSort) {
213 Arrays.sort(manifestRevs); 292 Arrays.sort(manifestRevs, 0, j);
214 } 293 }
215 return manifestRevs; 294 if (j == manifestRevs.length) {
295 return manifestRevs;
296 } else {
297 int[] rv = new int[j];
298 //Arrays.copyOfRange
299 System.arraycopy(manifestRevs, 0, rv, 0, j);
300 return rv;
301 }
216 } 302 }
217 303
218 public interface Inspector { 304 public interface Inspector {
219 boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision); 305 boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision);
220 /** 306 /**
475 } 561 }
476 } 562 }
477 for (int u : undefinedChangelogRevision) { 563 for (int u : undefinedChangelogRevision) {
478 try { 564 try {
479 Nodeid manifest = repo.getChangelog().range(u, u).get(0).manifest(); 565 Nodeid manifest = repo.getChangelog().range(u, u).get(0).manifest();
480 // FIXME calculate those missing effectively (e.g. cache and sort nodeids to speed lookup 566 // TODO calculate those missing effectively (e.g. cache and sort nodeids to speed lookup
481 // right away in the #next (may refactor ParentWalker's sequential and sorted into dedicated helper and reuse here) 567 // right away in the #next (may refactor ParentWalker's sequential and sorted into dedicated helper and reuse here)
482 if (manifest.isNull()) { 568 if (manifest.isNull()) {
483 repo.getContext().getLog().warn(getClass(), "Changeset %d has no associated manifest entry", u); 569 repo.getContext().getLog().warn(getClass(), "Changeset %d has no associated manifest entry", u);
484 // keep -1 in the changelog2manifest map. FIXME rest of the code shall accomodate to the fact manifest revision may be missing 570 // keep -1 in the changelog2manifest map.
485 } else { 571 } else {
486 changelog2manifest[u] = repo.getManifest().getRevisionIndex(manifest); 572 changelog2manifest[u] = repo.getManifest().getRevisionIndex(manifest);
487 } 573 }
488 } catch (HgInvalidControlFileException ex) { 574 } catch (HgInvalidControlFileException ex) {
489 // FIXME need to propagate the error up to client 575 // FIXME need to propagate the error up to client