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 }