Mercurial > jhg
comparison src/org/tmatesoft/hg/repo/HgStatusCollector.java @ 94:af1f3b78b918
*StatusCollector renamed to Hg*StatusCollector
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Thu, 27 Jan 2011 21:18:47 +0100 |
| parents | src/org/tmatesoft/hg/repo/StatusCollector.java@d55d4eedfc57 |
| children | a3a2e5deb320 |
comparison
equal
deleted
inserted
replaced
| 93:d55d4eedfc57 | 94:af1f3b78b918 |
|---|---|
| 1 /* | |
| 2 * Copyright (c) 2011 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@svnkit.com | |
| 16 */ | |
| 17 package org.tmatesoft.hg.repo; | |
| 18 | |
| 19 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION; | |
| 20 import static org.tmatesoft.hg.repo.HgRepository.TIP; | |
| 21 | |
| 22 import java.util.Collection; | |
| 23 import java.util.Collections; | |
| 24 import java.util.LinkedHashMap; | |
| 25 import java.util.LinkedList; | |
| 26 import java.util.List; | |
| 27 import java.util.Map; | |
| 28 import java.util.TreeMap; | |
| 29 import java.util.TreeSet; | |
| 30 | |
| 31 import org.tmatesoft.hg.core.Nodeid; | |
| 32 import org.tmatesoft.hg.core.Path; | |
| 33 import org.tmatesoft.hg.util.PathPool; | |
| 34 import org.tmatesoft.hg.util.PathRewrite; | |
| 35 | |
| 36 | |
| 37 /** | |
| 38 * RevisionWalker? | |
| 39 * | |
| 40 * @author Artem Tikhomirov | |
| 41 * @author TMate Software Ltd. | |
| 42 */ | |
| 43 public class HgStatusCollector { | |
| 44 | |
| 45 private final HgRepository repo; | |
| 46 private final Map<Integer, ManifestRevisionInspector> cache; // sparse array, in fact | |
| 47 private PathPool pathPool; | |
| 48 | |
| 49 public HgStatusCollector(HgRepository hgRepo) { | |
| 50 this.repo = hgRepo; | |
| 51 cache = new TreeMap<Integer, ManifestRevisionInspector>(); | |
| 52 ManifestRevisionInspector emptyFakeState = new ManifestRevisionInspector(); | |
| 53 emptyFakeState.begin(-1, null); | |
| 54 emptyFakeState.end(-1); // FIXME HgRepo.TIP == -1 as well, need to distinguish fake "prior to first" revision from "the very last" | |
| 55 cache.put(-1, emptyFakeState); | |
| 56 } | |
| 57 | |
| 58 public HgRepository getRepo() { | |
| 59 return repo; | |
| 60 } | |
| 61 | |
| 62 private ManifestRevisionInspector get(int rev) { | |
| 63 ManifestRevisionInspector i = cache.get(rev); | |
| 64 if (i == null) { | |
| 65 i = new ManifestRevisionInspector(); | |
| 66 cache.put(rev, i); | |
| 67 repo.getManifest().walk(rev, rev, i); | |
| 68 } | |
| 69 return i; | |
| 70 } | |
| 71 | |
| 72 /*package-local*/ ManifestRevisionInspector raw(int rev) { | |
| 73 return get(rev); | |
| 74 } | |
| 75 /*package-local*/ PathPool getPathPool() { | |
| 76 if (pathPool == null) { | |
| 77 pathPool = new PathPool(new PathRewrite.Empty()); | |
| 78 } | |
| 79 return pathPool; | |
| 80 } | |
| 81 | |
| 82 public void setPathPool(PathPool pathPool) { | |
| 83 this.pathPool = pathPool; | |
| 84 } | |
| 85 | |
| 86 | |
| 87 // hg status --change <rev> | |
| 88 public void change(int rev, HgStatusInspector inspector) { | |
| 89 int[] parents = new int[2]; | |
| 90 repo.getChangelog().parents(rev, parents, null, null); | |
| 91 walk(parents[0], rev, inspector); | |
| 92 } | |
| 93 | |
| 94 // I assume revision numbers are the same for changelog and manifest - here | |
| 95 // user would like to pass changelog revision numbers, and I use them directly to walk manifest. | |
| 96 // if this assumption is wrong, fix this (lookup manifest revisions from changeset). | |
| 97 public void walk(int rev1, int rev2, HgStatusInspector inspector) { | |
| 98 if (rev1 == rev2) { | |
| 99 throw new IllegalArgumentException(); | |
| 100 } | |
| 101 if (inspector == null) { | |
| 102 throw new IllegalArgumentException(); | |
| 103 } | |
| 104 if (inspector instanceof Record) { | |
| 105 ((Record) inspector).init(rev1, rev2, this); | |
| 106 } | |
| 107 if (rev1 == TIP) { | |
| 108 rev1 = repo.getManifest().getRevisionCount() - 1; | |
| 109 } | |
| 110 if (rev2 == TIP) { | |
| 111 rev2 = repo.getManifest().getRevisionCount() - 1; // XXX add Revlog.tip() func ? | |
| 112 } | |
| 113 // in fact, rev1 and rev2 are often next (or close) to each other, | |
| 114 // thus, we can optimize Manifest reads here (manifest.walk(rev1, rev2)) | |
| 115 ManifestRevisionInspector r1, r2 ; | |
| 116 if (!cache.containsKey(rev1) && !cache.containsKey(rev2) && Math.abs(rev1 - rev2) < 5 /*subjective equivalent of 'close enough'*/) { | |
| 117 int minRev = rev1 < rev2 ? rev1 : rev2; | |
| 118 int maxRev = minRev == rev1 ? rev2 : rev1; | |
| 119 if (minRev > 0) { | |
| 120 minRev--; // expand range a bit | |
| 121 // XXX perhaps, if revlog.baseRevision is cheap, shall expand minRev up to baseRevision | |
| 122 // which gonna be read anyway | |
| 123 } | |
| 124 | |
| 125 repo.getManifest().walk(minRev, maxRev, new HgManifest.Inspector() { | |
| 126 private ManifestRevisionInspector delegate; | |
| 127 | |
| 128 public boolean begin(int revision, Nodeid nid) { | |
| 129 cache.put(revision, delegate = new ManifestRevisionInspector()); | |
| 130 delegate.begin(revision, nid); | |
| 131 return true; | |
| 132 } | |
| 133 | |
| 134 public boolean next(Nodeid nid, String fname, String flags) { | |
| 135 delegate.next(nid, fname, flags); | |
| 136 return true; | |
| 137 } | |
| 138 | |
| 139 public boolean end(int revision) { | |
| 140 delegate.end(revision); | |
| 141 delegate = null; | |
| 142 return true; | |
| 143 } | |
| 144 }); | |
| 145 } | |
| 146 r1 = get(rev1); | |
| 147 r2 = get(rev2); | |
| 148 | |
| 149 PathPool pp = getPathPool(); | |
| 150 | |
| 151 TreeSet<String> r1Files = new TreeSet<String>(r1.files()); | |
| 152 for (String fname : r2.files()) { | |
| 153 if (r1Files.remove(fname)) { | |
| 154 Nodeid nidR1 = r1.nodeid(fname); | |
| 155 Nodeid nidR2 = r2.nodeid(fname); | |
| 156 String flagsR1 = r1.flags(fname); | |
| 157 String flagsR2 = r2.flags(fname); | |
| 158 if (nidR1.equals(nidR2) && ((flagsR2 == null && flagsR1 == null) || flagsR2.equals(flagsR1))) { | |
| 159 inspector.clean(pp.path(fname)); | |
| 160 } else { | |
| 161 inspector.modified(pp.path(fname)); | |
| 162 } | |
| 163 } else { | |
| 164 String copyOrigin = getOriginIfCopy(repo, fname, r1Files, rev1); | |
| 165 if (copyOrigin != null) { | |
| 166 inspector.copied(pp.path(copyOrigin), pp.path(fname)); | |
| 167 } else { | |
| 168 inspector.added(pp.path(fname)); | |
| 169 } | |
| 170 } | |
| 171 } | |
| 172 for (String left : r1Files) { | |
| 173 inspector.removed(pp.path(left)); | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 public Record status(int rev1, int rev2) { | |
| 178 Record rv = new Record(); | |
| 179 walk(rev1, rev2, rv); | |
| 180 return rv; | |
| 181 } | |
| 182 | |
| 183 /*package-local*/static String getOriginIfCopy(HgRepository hgRepo, String fname, Collection<String> originals, int originalChangelogRevision) { | |
| 184 HgDataFile df = hgRepo.getFileNode(fname); | |
| 185 while (df.isCopy()) { | |
| 186 Path original = df.getCopySourceName(); | |
| 187 if (originals.contains(original.toString())) { | |
| 188 df = hgRepo.getFileNode(original); | |
| 189 int changelogRevision = df.getChangesetLocalRevision(0); | |
| 190 if (changelogRevision <= originalChangelogRevision) { | |
| 191 // copy/rename source was known prior to rev1 | |
| 192 // (both r1Files.contains is true and original was created earlier than rev1) | |
| 193 // without r1Files.contains changelogRevision <= rev1 won't suffice as the file | |
| 194 // might get removed somewhere in between (changelogRevision < R < rev1) | |
| 195 return original.toString(); | |
| 196 } | |
| 197 break; // copy/rename done later | |
| 198 } | |
| 199 df = hgRepo.getFileNode(original); // try more steps away | |
| 200 } | |
| 201 return null; | |
| 202 } | |
| 203 | |
| 204 // XXX for r1..r2 status, only modified, added, removed (and perhaps, clean) make sense | |
| 205 // XXX Need to specify whether copy targets are in added or not (@see Inspector#copied above) | |
| 206 public static class Record implements HgStatusInspector { | |
| 207 private List<Path> modified, added, removed, clean, missing, unknown, ignored; | |
| 208 private Map<Path, Path> copied; | |
| 209 | |
| 210 private int startRev, endRev; | |
| 211 private HgStatusCollector statusHelper; | |
| 212 | |
| 213 // XXX StatusCollector may additionally initialize Record instance to speed lookup of changed file revisions | |
| 214 // here I need access to ManifestRevisionInspector via #raw(). Perhaps, non-static class (to get | |
| 215 // implicit reference to StatusCollector) may be better? | |
| 216 // Since users may want to reuse Record instance we've once created (and initialized), we need to | |
| 217 // ensure functionality is correct for each/any call (#walk checks instanceof Record and fixes it up) | |
| 218 // Perhaps, distinct helper (sc.getRevisionHelper().nodeid(fname)) would be better, just not clear | |
| 219 // how to supply [start..end] values there easily | |
| 220 /*package-local*/void init(int startRevision, int endRevision, HgStatusCollector self) { | |
| 221 startRev = startRevision; | |
| 222 endRev = endRevision; | |
| 223 statusHelper = self; | |
| 224 } | |
| 225 | |
| 226 public Nodeid nodeidBeforeChange(Path fname) { | |
| 227 if (statusHelper == null || startRev == BAD_REVISION) { | |
| 228 return null; | |
| 229 } | |
| 230 if ((modified == null || !modified.contains(fname)) && (removed == null || !removed.contains(fname))) { | |
| 231 return null; | |
| 232 } | |
| 233 return statusHelper.raw(startRev).nodeid(fname.toString()); | |
| 234 } | |
| 235 public Nodeid nodeidAfterChange(Path fname) { | |
| 236 if (statusHelper == null || endRev == BAD_REVISION) { | |
| 237 return null; | |
| 238 } | |
| 239 if ((modified == null || !modified.contains(fname)) && (added == null || !added.contains(fname))) { | |
| 240 return null; | |
| 241 } | |
| 242 return statusHelper.raw(endRev).nodeid(fname.toString()); | |
| 243 } | |
| 244 | |
| 245 public List<Path> getModified() { | |
| 246 return proper(modified); | |
| 247 } | |
| 248 | |
| 249 public List<Path> getAdded() { | |
| 250 return proper(added); | |
| 251 } | |
| 252 | |
| 253 public List<Path> getRemoved() { | |
| 254 return proper(removed); | |
| 255 } | |
| 256 | |
| 257 public Map<Path,Path> getCopied() { | |
| 258 if (copied == null) { | |
| 259 return Collections.emptyMap(); | |
| 260 } | |
| 261 return Collections.unmodifiableMap(copied); | |
| 262 } | |
| 263 | |
| 264 public List<Path> getClean() { | |
| 265 return proper(clean); | |
| 266 } | |
| 267 | |
| 268 public List<Path> getMissing() { | |
| 269 return proper(missing); | |
| 270 } | |
| 271 | |
| 272 public List<Path> getUnknown() { | |
| 273 return proper(unknown); | |
| 274 } | |
| 275 | |
| 276 public List<Path> getIgnored() { | |
| 277 return proper(ignored); | |
| 278 } | |
| 279 | |
| 280 private List<Path> proper(List<Path> l) { | |
| 281 if (l == null) { | |
| 282 return Collections.emptyList(); | |
| 283 } | |
| 284 return Collections.unmodifiableList(l); | |
| 285 } | |
| 286 | |
| 287 // | |
| 288 // | |
| 289 | |
| 290 public void modified(Path fname) { | |
| 291 modified = doAdd(modified, fname); | |
| 292 } | |
| 293 | |
| 294 public void added(Path fname) { | |
| 295 added = doAdd(added, fname); | |
| 296 } | |
| 297 | |
| 298 public void copied(Path fnameOrigin, Path fnameAdded) { | |
| 299 if (copied == null) { | |
| 300 copied = new LinkedHashMap<Path, Path>(); | |
| 301 } | |
| 302 added(fnameAdded); | |
| 303 copied.put(fnameAdded, fnameOrigin); | |
| 304 } | |
| 305 | |
| 306 public void removed(Path fname) { | |
| 307 removed = doAdd(removed, fname); | |
| 308 } | |
| 309 | |
| 310 public void clean(Path fname) { | |
| 311 clean = doAdd(clean, fname); | |
| 312 } | |
| 313 | |
| 314 public void missing(Path fname) { | |
| 315 missing = doAdd(missing, fname); | |
| 316 } | |
| 317 | |
| 318 public void unknown(Path fname) { | |
| 319 unknown = doAdd(unknown, fname); | |
| 320 } | |
| 321 | |
| 322 public void ignored(Path fname) { | |
| 323 ignored = doAdd(ignored, fname); | |
| 324 } | |
| 325 | |
| 326 private static List<Path> doAdd(List<Path> l, Path p) { | |
| 327 if (l == null) { | |
| 328 l = new LinkedList<Path>(); | |
| 329 } | |
| 330 l.add(p); | |
| 331 return l; | |
| 332 } | |
| 333 } | |
| 334 | |
| 335 /*package-local*/ static final class ManifestRevisionInspector implements HgManifest.Inspector { | |
| 336 private final TreeMap<String, Nodeid> idsMap; | |
| 337 private final TreeMap<String, String> flagsMap; | |
| 338 | |
| 339 public ManifestRevisionInspector() { | |
| 340 idsMap = new TreeMap<String, Nodeid>(); | |
| 341 flagsMap = new TreeMap<String, String>(); | |
| 342 } | |
| 343 | |
| 344 public Collection<String> files() { | |
| 345 return idsMap.keySet(); | |
| 346 } | |
| 347 | |
| 348 public Nodeid nodeid(String fname) { | |
| 349 return idsMap.get(fname); | |
| 350 } | |
| 351 | |
| 352 public String flags(String fname) { | |
| 353 return flagsMap.get(fname); | |
| 354 } | |
| 355 | |
| 356 // | |
| 357 | |
| 358 public boolean next(Nodeid nid, String fname, String flags) { | |
| 359 idsMap.put(fname, nid); | |
| 360 flagsMap.put(fname, flags); | |
| 361 return true; | |
| 362 } | |
| 363 | |
| 364 public boolean end(int revision) { | |
| 365 // in fact, this class cares about single revision | |
| 366 return false; | |
| 367 } | |
| 368 | |
| 369 public boolean begin(int revision, Nodeid nid) { | |
| 370 return true; | |
| 371 } | |
| 372 } | |
| 373 | |
| 374 } |
