comparison src/org/tmatesoft/hg/repo/HgChangelog.java @ 153:ab7ea2ac21cb

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