Mercurial > hg4j
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 } |