Mercurial > jhg
comparison src/org/tmatesoft/hg/repo/HgDataFile.java @ 78:c25c5c348d1b
Skip metadata in the beginning of a file content. Parse metadata, recognize copies/renames
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Tue, 25 Jan 2011 02:13:53 +0100 |
| parents | c677e1593919 |
| children | 5f9635c01681 |
comparison
equal
deleted
inserted
replaced
| 77:c677e1593919 | 78:c25c5c348d1b |
|---|---|
| 16 */ | 16 */ |
| 17 package org.tmatesoft.hg.repo; | 17 package org.tmatesoft.hg.repo; |
| 18 | 18 |
| 19 import static org.tmatesoft.hg.repo.HgRepository.TIP; | 19 import static org.tmatesoft.hg.repo.HgRepository.TIP; |
| 20 | 20 |
| 21 import java.util.ArrayList; | |
| 22 import java.util.Collection; | |
| 23 import java.util.HashMap; | |
| 24 | |
| 21 import org.tmatesoft.hg.core.Nodeid; | 25 import org.tmatesoft.hg.core.Nodeid; |
| 22 import org.tmatesoft.hg.core.Path; | 26 import org.tmatesoft.hg.core.Path; |
| 23 import org.tmatesoft.hg.internal.RevlogStream; | 27 import org.tmatesoft.hg.internal.RevlogStream; |
| 24 | 28 |
| 25 | 29 |
| 34 | 38 |
| 35 // absolute from repo root? | 39 // absolute from repo root? |
| 36 // slashes, unix-style? | 40 // slashes, unix-style? |
| 37 // repo location agnostic, just to give info to user, not to access real storage | 41 // repo location agnostic, just to give info to user, not to access real storage |
| 38 private final Path path; | 42 private final Path path; |
| 43 private Metadata metadata; | |
| 39 | 44 |
| 40 /*package-local*/HgDataFile(HgRepository hgRepo, Path path, RevlogStream content) { | 45 /*package-local*/HgDataFile(HgRepository hgRepo, Path path, RevlogStream content) { |
| 41 super(hgRepo, content); | 46 super(hgRepo, content); |
| 42 this.path = path; | 47 this.path = path; |
| 43 } | 48 } |
| 55 return content.dataLength(getLocalRevisionNumber(nodeid)); | 60 return content.dataLength(getLocalRevisionNumber(nodeid)); |
| 56 } | 61 } |
| 57 | 62 |
| 58 public byte[] content() { | 63 public byte[] content() { |
| 59 return content(TIP); | 64 return content(TIP); |
| 65 } | |
| 66 | |
| 67 // for data files need to check heading of the file content for possible metadata | |
| 68 // @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8- | |
| 69 @Override | |
| 70 public byte[] content(int revision) { | |
| 71 if (revision == TIP) { | |
| 72 revision = content.revisionCount() - 1; // FIXME maxRevision. | |
| 73 } | |
| 74 byte[] data = super.content(revision); | |
| 75 if (data.length < 4 || (data[0] != 1 && data[1] != 10)) { | |
| 76 return data; | |
| 77 } | |
| 78 int toSkip = 0; | |
| 79 if (metadata == null || !metadata.known(revision)) { | |
| 80 int lastEntryStart = 2; | |
| 81 int lastColon = -1; | |
| 82 ArrayList<MetadataEntry> _metadata = new ArrayList<MetadataEntry>(); | |
| 83 String key = null, value = null; | |
| 84 for (int i = 2; i < data.length; i++) { | |
| 85 if (data[i] == (int) ':') { | |
| 86 key = new String(data, lastEntryStart, i - lastEntryStart); | |
| 87 lastColon = i; | |
| 88 } else if (data[i] == '\n') { | |
| 89 if (key == null || lastColon == -1 || i <= lastColon) { | |
| 90 throw new IllegalStateException(); // FIXME log instead and record null key in the metadata. Ex just to fail fast during dev | |
| 91 } | |
| 92 value = new String(data, lastColon + 1, i - lastColon - 1).trim(); | |
| 93 _metadata.add(new MetadataEntry(key, value)); | |
| 94 key = value = null; | |
| 95 lastColon = -1; | |
| 96 lastEntryStart = i+1; | |
| 97 } else if (data[i] == 1 && i + 1 < data.length && data[i+1] == 10) { | |
| 98 if (key != null && lastColon != -1 && i > lastColon) { | |
| 99 // just in case last entry didn't end with newline | |
| 100 value = new String(data, lastColon + 1, i - lastColon - 1); | |
| 101 _metadata.add(new MetadataEntry(key, value)); | |
| 102 } | |
| 103 lastEntryStart = i+1; | |
| 104 break; | |
| 105 } | |
| 106 } | |
| 107 _metadata.trimToSize(); | |
| 108 if (metadata == null) { | |
| 109 metadata = new Metadata(); | |
| 110 } | |
| 111 metadata.add(revision, lastEntryStart, _metadata); | |
| 112 toSkip = lastEntryStart; | |
| 113 } else { | |
| 114 toSkip = metadata.dataOffset(revision); | |
| 115 } | |
| 116 // XXX copy of an array may be memory-hostile, a wrapper with baseOffsetShift(lastEntryStart) would be more convenient | |
| 117 byte[] rv = new byte[data.length - toSkip]; | |
| 118 System.arraycopy(data, toSkip, rv, 0, rv.length); | |
| 119 return rv; | |
| 60 } | 120 } |
| 61 | 121 |
| 62 public void history(Changeset.Inspector inspector) { | 122 public void history(Changeset.Inspector inspector) { |
| 63 history(0, content.revisionCount() - 1, inspector); | 123 history(0, content.revisionCount() - 1, inspector); |
| 64 } | 124 } |
| 85 } | 145 } |
| 86 }; | 146 }; |
| 87 content.iterate(start, end, false, insp); | 147 content.iterate(start, end, false, insp); |
| 88 getRepo().getChangelog().range(inspector, commitRevisions); | 148 getRepo().getChangelog().range(inspector, commitRevisions); |
| 89 } | 149 } |
| 150 | |
| 151 public boolean isCopy() { | |
| 152 if (metadata == null) { | |
| 153 content(0); // FIXME expensive way to find out metadata, distinct RevlogStream.Iterator would be better. | |
| 154 } | |
| 155 if (metadata == null || !metadata.known(0)) { | |
| 156 return false; | |
| 157 } | |
| 158 return metadata.find(0, "copy") != null; | |
| 159 } | |
| 160 | |
| 161 public Path getCopySourceName() { | |
| 162 if (isCopy()) { | |
| 163 return Path.create(metadata.find(0, "copy")); | |
| 164 } | |
| 165 throw new UnsupportedOperationException(); // XXX REVISIT, think over if Exception is good (clients would check isCopy() anyway, perhaps null is sufficient?) | |
| 166 } | |
| 167 | |
| 168 public Nodeid getCopySourceRevision() { | |
| 169 if (isCopy()) { | |
| 170 return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid | |
| 171 } | |
| 172 throw new UnsupportedOperationException(); | |
| 173 } | |
| 174 | |
| 175 public static final class MetadataEntry { | |
| 176 private final String entry; | |
| 177 private final int valueStart; | |
| 178 /*package-local*/MetadataEntry(String key, String value) { | |
| 179 entry = key + value; | |
| 180 valueStart = key.length(); | |
| 181 } | |
| 182 /*package-local*/boolean matchKey(String key) { | |
| 183 return key.length() == valueStart && entry.startsWith(key); | |
| 184 } | |
| 185 public String key() { | |
| 186 return entry.substring(0, valueStart); | |
| 187 } | |
| 188 public String value() { | |
| 189 return entry.substring(valueStart); | |
| 190 } | |
| 191 } | |
| 192 | |
| 193 private static class Metadata { | |
| 194 // XXX sparse array needed | |
| 195 private final HashMap<Integer, Integer> offsets = new HashMap<Integer, Integer>(5); | |
| 196 private final HashMap<Integer, MetadataEntry[]> entries = new HashMap<Integer, MetadataEntry[]>(5); | |
| 197 boolean known(int revision) { | |
| 198 return offsets.containsKey(revision); | |
| 199 } | |
| 200 // since this is internal class, callers are supposed to ensure arg correctness (i.e. ask known() before) | |
| 201 int dataOffset(int revision) { | |
| 202 return offsets.get(revision); | |
| 203 } | |
| 204 void add(int revision, int dataOffset, Collection<MetadataEntry> e) { | |
| 205 offsets.put(revision, dataOffset); | |
| 206 entries.put(revision, e.toArray(new MetadataEntry[e.size()])); | |
| 207 } | |
| 208 String find(int revision, String key) { | |
| 209 for (MetadataEntry me : entries.get(revision)) { | |
| 210 if (me.matchKey(key)) { | |
| 211 return me.value(); | |
| 212 } | |
| 213 } | |
| 214 return null; | |
| 215 } | |
| 216 } | |
| 90 } | 217 } |
