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 |
