comparison src/org/tmatesoft/hg/repo/HgChangelog.java @ 129:645829962785

core.Cset renamed to HgChangeset; repo.Changeset moved into HgChangelog
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 16 Feb 2011 20:32:29 +0100
parents a3a2e5deb320
children 3959bffb14e9
comparison
equal deleted inserted replaced
128:44b97930570c 129:645829962785
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 java.io.UnsupportedEncodingException;
19 import java.util.ArrayList; 20 import java.util.ArrayList;
20 import java.util.Arrays; 21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.Date;
24 import java.util.Formatter;
25 import java.util.HashMap;
21 import java.util.List; 26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Map;
22 29
23 import org.tmatesoft.hg.core.Nodeid; 30 import org.tmatesoft.hg.core.Nodeid;
24 import org.tmatesoft.hg.internal.RevlogStream; 31 import org.tmatesoft.hg.internal.RevlogStream;
25 32
26 33
34 41
35 /*package-local*/ HgChangelog(HgRepository hgRepo, RevlogStream content) { 42 /*package-local*/ HgChangelog(HgRepository hgRepo, RevlogStream content) {
36 super(hgRepo, content); 43 super(hgRepo, content);
37 } 44 }
38 45
39 public void all(final Changeset.Inspector inspector) { 46 public void all(final HgChangelog.Inspector inspector) {
40 range(0, content.revisionCount() - 1, inspector); 47 range(0, content.revisionCount() - 1, inspector);
41 } 48 }
42 49
43 public void range(int start, int end, final Changeset.Inspector inspector) { 50 public void range(int start, int end, final HgChangelog.Inspector inspector) {
44 RevlogStream.Inspector i = new RevlogStream.Inspector() { 51 RevlogStream.Inspector i = new RevlogStream.Inspector() {
45 52
46 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) { 53 public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) {
47 Changeset cset = Changeset.parse(data, 0, data.length); 54 Changeset cset = Changeset.parse(data, 0, data.length);
48 // XXX there's no guarantee for Changeset.Callback that distinct instance comes each time, consider instance reuse 55 // XXX there's no guarantee for Changeset.Callback that distinct instance comes each time, consider instance reuse
63 }; 70 };
64 content.iterate(start, end, true, i); 71 content.iterate(start, end, true, i);
65 return rv; 72 return rv;
66 } 73 }
67 74
68 public void range(final Changeset.Inspector inspector, final int... revisions) { 75 public void range(final HgChangelog.Inspector inspector, final int... revisions) {
69 if (revisions == null || revisions.length == 0) { 76 if (revisions == null || revisions.length == 0) {
70 return; 77 return;
71 } 78 }
72 RevlogStream.Inspector i = new RevlogStream.Inspector() { 79 RevlogStream.Inspector i = new RevlogStream.Inspector() {
73 80
79 } 86 }
80 }; 87 };
81 Arrays.sort(revisions); 88 Arrays.sort(revisions);
82 content.iterate(revisions[0], revisions[revisions.length - 1], true, i); 89 content.iterate(revisions[0], revisions[revisions.length - 1], true, i);
83 } 90 }
91
92
93 public interface Inspector {
94 // TODO describe whether cset is new instance each time
95 void next(int revisionNumber, Nodeid nodeid, Changeset cset);
96 }
97
98
99 /**
100 * Entry in the Changelog
101 */
102 public static class Changeset implements Cloneable /* for those that would like to keep a copy */{
103 // TODO immutable
104 private/* final */Nodeid manifest;
105 private String user;
106 private String comment;
107 private List<String> files; // unmodifiable collection (otherwise #files() and implicit #clone() shall be revised)
108 private Date time;
109 private int timezone; // not sure it's of any use
110 private Map<String, String> extras;
111
112 /**
113 * @see mercurial/changelog.py:read()
114 *
115 * <pre>
116 * format used:
117 * nodeid\n : manifest node in ascii
118 * user\n : user, no \n or \r allowed
119 * time tz extra\n : date (time is int or float, timezone is int)
120 * : extra is metadatas, encoded and separated by '\0'
121 * : older versions ignore it
122 * files\n\n : files modified by the cset, no \n or \r allowed
123 * (.*) : comment (free text, ideally utf-8)
124 *
125 * changelog v0 doesn't use extra
126 * </pre>
127 */
128 private Changeset() {
129 }
130
131 public Nodeid manifest() {
132 return manifest;
133 }
134
135 public String user() {
136 return user;
137 }
138
139 public String comment() {
140 return comment;
141 }
142
143 public List<String> files() {
144 return files;
145 }
146
147 public Date date() {
148 return time;
149 }
150
151 public String dateString() {
152 StringBuilder sb = new StringBuilder(30);
153 Formatter f = new Formatter(sb, Locale.US);
154 f.format("%ta %<tb %<td %<tH:%<tM:%<tS %<tY %<tz", time);
155 return sb.toString();
156 }
157
158 public Map<String, String> extras() {
159 return extras;
160 }
161
162 public String branch() {
163 return extras.get("branch");
164 }
165
166 @Override
167 public String toString() {
168 StringBuilder sb = new StringBuilder();
169 sb.append("Changeset {");
170 sb.append("User: ").append(user).append(", ");
171 sb.append("Comment: ").append(comment).append(", ");
172 sb.append("Manifest: ").append(manifest).append(", ");
173 sb.append("Date: ").append(time).append(", ");
174 sb.append("Files: ").append(files.size());
175 for (String s : files) {
176 sb.append(", ").append(s);
177 }
178 if (extras != null) {
179 sb.append(", Extra: ").append(extras);
180 }
181 sb.append("}");
182 return sb.toString();
183 }
184
185 @Override
186 public Changeset clone() {
187 try {
188 return (Changeset) super.clone();
189 } catch (CloneNotSupportedException ex) {
190 throw new InternalError(ex.toString());
191 }
192 }
193
194 public static Changeset parse(byte[] data, int offset, int length) {
195 Changeset rv = new Changeset();
196 rv.init(data, offset, length);
197 return rv;
198 }
199
200 /* package-local */void init(byte[] data, int offset, int length) {
201 final int bufferEndIndex = offset + length;
202 final byte lineBreak = (byte) '\n';
203 int breakIndex1 = indexOf(data, lineBreak, offset, bufferEndIndex);
204 if (breakIndex1 == -1) {
205 throw new IllegalArgumentException("Bad Changeset data");
206 }
207 Nodeid _nodeid = Nodeid.fromAscii(data, 0, breakIndex1);
208 int breakIndex2 = indexOf(data, lineBreak, breakIndex1 + 1, bufferEndIndex);
209 if (breakIndex2 == -1) {
210 throw new IllegalArgumentException("Bad Changeset data");
211 }
212 String _user = new String(data, breakIndex1 + 1, breakIndex2 - breakIndex1 - 1);
213 int breakIndex3 = indexOf(data, lineBreak, breakIndex2 + 1, bufferEndIndex);
214 if (breakIndex3 == -1) {
215 throw new IllegalArgumentException("Bad Changeset data");
216 }
217 String _timeString = new String(data, breakIndex2 + 1, breakIndex3 - breakIndex2 - 1);
218 int space1 = _timeString.indexOf(' ');
219 if (space1 == -1) {
220 throw new IllegalArgumentException("Bad Changeset data");
221 }
222 int space2 = _timeString.indexOf(' ', space1 + 1);
223 if (space2 == -1) {
224 space2 = _timeString.length();
225 }
226 long unixTime = Long.parseLong(_timeString.substring(0, space1)); // XXX Float, perhaps
227 int _timezone = Integer.parseInt(_timeString.substring(space1 + 1, space2));
228 // XXX not sure need to add timezone here - I can't figure out whether Hg keeps GMT time, and records timezone just for info, or unixTime is taken local
229 // on commit and timezone is recorded to adjust it to UTC.
230 Date _time = new Date(unixTime * 1000);
231 String _extras = space2 < _timeString.length() ? _timeString.substring(space2 + 1) : null;
232 Map<String, String> _extrasMap;
233 if (_extras == null) {
234 _extrasMap = Collections.singletonMap("branch", "default");
235 } else {
236 _extrasMap = new HashMap<String, String>();
237 for (String pair : _extras.split("\00")) {
238 int eq = pair.indexOf(':');
239 // FIXME need to decode key/value, @see changelog.py:decodeextra
240 _extrasMap.put(pair.substring(0, eq), pair.substring(eq + 1));
241 }
242 if (!_extrasMap.containsKey("branch")) {
243 _extrasMap.put("branch", "default");
244 }
245 _extrasMap = Collections.unmodifiableMap(_extrasMap);
246 }
247
248 //
249 int lastStart = breakIndex3 + 1;
250 int breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
251 ArrayList<String> _files = null;
252 if (breakIndex4 > lastStart) {
253 // if breakIndex4 == lastStart, we already found \n\n and hence there are no files (e.g. merge revision)
254 _files = new ArrayList<String>(5);
255 while (breakIndex4 != -1 && breakIndex4 + 1 < bufferEndIndex) {
256 _files.add(new String(data, lastStart, breakIndex4 - lastStart));
257 lastStart = breakIndex4 + 1;
258 if (data[breakIndex4 + 1] == lineBreak) {
259 // found \n\n
260 break;
261 } else {
262 breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
263 }
264 }
265 if (breakIndex4 == -1 || breakIndex4 >= bufferEndIndex) {
266 throw new IllegalArgumentException("Bad Changeset data");
267 }
268 } else {
269 breakIndex4--;
270 }
271 String _comment;
272 try {
273 _comment = new String(data, breakIndex4 + 2, bufferEndIndex - breakIndex4 - 2, "UTF-8");
274 // FIXME respect ui.fallbackencoding and try to decode if set
275 } catch (UnsupportedEncodingException ex) {
276 _comment = "";
277 throw new IllegalStateException("Could hardly happen");
278 }
279 // change this instance at once, don't leave it partially changes in case of error
280 this.manifest = _nodeid;
281 this.user = _user;
282 this.time = _time;
283 this.timezone = _timezone;
284 this.files = _files == null ? Collections.<String> emptyList() : Collections.unmodifiableList(_files);
285 this.comment = _comment;
286 this.extras = _extrasMap;
287 }
288
289 private static int indexOf(byte[] src, byte what, int startOffset, int endIndex) {
290 for (int i = startOffset; i < endIndex; i++) {
291 if (src[i] == what) {
292 return i;
293 }
294 }
295 return -1;
296 }
297 }
298
84 } 299 }