Mercurial > jhg
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 |