changeset 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 44b97930570c
children 7567f4a42fe5
files TODO cmdline/org/tmatesoft/hg/console/Log.java design.txt src/org/tmatesoft/hg/core/Cset.java src/org/tmatesoft/hg/core/HgChangeset.java src/org/tmatesoft/hg/core/HgStatus.java src/org/tmatesoft/hg/core/LogCommand.java src/org/tmatesoft/hg/internal/ChangelogHelper.java src/org/tmatesoft/hg/internal/KeywordFilter.java src/org/tmatesoft/hg/repo/Changeset.java src/org/tmatesoft/hg/repo/HgBundle.java src/org/tmatesoft/hg/repo/HgChangelog.java src/org/tmatesoft/hg/repo/HgDataFile.java test/org/tmatesoft/hg/test/TestHistory.java
diffstat 14 files changed, 439 insertions(+), 450 deletions(-) [+]
line wrap: on
line diff
--- a/TODO	Wed Feb 16 20:13:41 2011 +0100
+++ b/TODO	Wed Feb 16 20:32:29 2011 +0100
@@ -42,10 +42,9 @@
   - force translation if inconsistent (now either fails or does nothing)
 
 * API
-  - rename in .core Cset -> HgChangeset,
-  - rename in .repo Changeset to HgChangelog.Changeset, Changeset.Inspector -> HgChangelog.Inspector
   - CommandContext
   - Data access - not bytes, but ByteChannel
+  - HgRepository constants (TIP, BAD, WC) to HgRevisions enum
 
 * defects
   ConfigFile to strip comments from values (#)
--- a/cmdline/org/tmatesoft/hg/console/Log.java	Wed Feb 16 20:13:41 2011 +0100
+++ b/cmdline/org/tmatesoft/hg/console/Log.java	Wed Feb 16 20:32:29 2011 +0100
@@ -20,7 +20,7 @@
 import java.util.LinkedList;
 import java.util.List;
 
-import org.tmatesoft.hg.core.Cset;
+import org.tmatesoft.hg.core.HgChangeset;
 import org.tmatesoft.hg.core.LogCommand;
 import org.tmatesoft.hg.core.LogCommand.FileRevision;
 import org.tmatesoft.hg.core.Nodeid;
@@ -135,7 +135,7 @@
 			System.out.printf("Got notified that %s(%s) was originally known as %s(%s)\n", to.getPath(), to.getRevision(), from.getPath(), from.getRevision());
 		}
 
-		public void next(Cset changeset) {
+		public void next(HgChangeset changeset) {
 			final String s = print(changeset);
 			if (reverseOrder) {
 				// XXX in fact, need to insert s into l according to changeset.getRevision()
@@ -158,7 +158,7 @@
 			changelogWalker = null;
 		}
 
-		private String print(Cset cset) {
+		private String print(HgChangeset cset) {
 			StringBuilder sb = new StringBuilder();
 			Formatter f = new Formatter(sb);
 			final Nodeid csetNodeid = cset.getNodeid();
--- a/design.txt	Wed Feb 16 20:13:41 2011 +0100
+++ b/design.txt	Wed Feb 16 20:32:29 2011 +0100
@@ -44,8 +44,6 @@
 Strip off metadata from beg of the stream - DataAccess (with rebase/moveBaseOffset(int)) would be handy
 Parameterize StatusCollector to produce copy only when needed. And HgDataFile.metadata perhaps should be moved to cacheable place?
 
-RevisionMap to replace TreeMap<Integer, ?> 
-
 Status operation from GUI - guess, usually on a file/subfolder, hence API should allow for starting path (unlike cmdline, seems useless to implement include/exclide patterns - GUI users hardly enter them, ever)
   -> recently introduced FileWalker may perhaps help solving this (if starts walking from selected folder) for status op against WorkingDir?
 
@@ -76,9 +74,19 @@
 * NIO and mapped files - should be fast. Although seems to give less control on mem usage. 
 * Regular InputStreams and chunked stream on top - allocate List<byte[]>, each (but last) chunk of fixed size (depending on initial file size) 
 
+
+* API
+  + rename in .core Cset -> HgChangeset,
+  + rename in .repo Changeset to HgChangelog.Changeset, Changeset.Inspector -> HgChangelog.Inspector
+  - CommandContext
+  - Data access - not bytes, but ByteChannel
+  - HgRepository constants (TIP, BAD, WC) to HgRevisions enum
+  - RevisionMap to replace TreeMap<Integer, ?> 
+
 <<<<<
 
 Tests:
 DataAccess - readBytes(length > memBufferSize, length*2 > memBufferSize) - to check impl is capable to read huge chunks of data, regardless of own buffer size
 
-ExecHelper('cmd', OutputParser()).run(). StatusOutputParser, LogOutputParser extends OutputParser. construct java result similar to that of cmd, compare results
\ No newline at end of file
+ExecHelper('cmd', OutputParser()).run(). StatusOutputParser, LogOutputParser extends OutputParser. construct java result similar to that of cmd, compare results
+
--- a/src/org/tmatesoft/hg/core/Cset.java	Wed Feb 16 20:13:41 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +0,0 @@
-/*
- * Copyright (c) 2011 TMate Software Ltd
- *  
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * For information on how to redistribute this software under
- * the terms of a license other than GNU General Public License
- * contact TMate Software at support@hg4j.com
- */
-package org.tmatesoft.hg.core;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.tmatesoft.hg.core.LogCommand.FileRevision;
-import org.tmatesoft.hg.repo.Changeset;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.repo.HgStatusCollector;
-import org.tmatesoft.hg.util.PathPool;
-
-
-/**
- * TODO rename to Changeset along with original Changeset moved to .repo and renamed to HgChangeset?
- * Not thread-safe, don't try to read from different threads
- * 
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Cset implements Cloneable {
-	private final HgStatusCollector statusHelper;
-	private final PathPool pathHelper;
-
-	//
-	private Changeset changeset;
-	private Nodeid nodeid;
-
-	//
-	private List<FileRevision> modifiedFiles, addedFiles;
-	private List<Path> deletedFiles;
-	private int revNumber;
-
-	// XXX consider CommandContext with StatusCollector, PathPool etc. Commands optionally get CC through a cons or create new
-	// and pass it around
-	/*package-local*/Cset(HgStatusCollector statusCollector, PathPool pathPool) {
-		statusHelper = statusCollector;
-		pathHelper = pathPool;
-	}
-	
-	/*package-local*/
-	void init(int localRevNumber, Nodeid nid, Changeset rawChangeset) {
-		revNumber = localRevNumber;
-		nodeid = nid;
-		changeset = rawChangeset;
-		modifiedFiles = addedFiles = null;
-		deletedFiles = null;
-	}
-	public int getRevision() {
-		return revNumber;
-	}
-	public Nodeid getNodeid() {
-		return nodeid;
-	}
-	public String getUser() {
-		return changeset.user();
-	}
-	public String getComment() {
-		return changeset.comment();
-	}
-	public String getBranch() {
-		return changeset.branch();
-	}
-	public String getDate() {
-		return changeset.dateString();
-	}
-	public Nodeid getManifestRevision() {
-		return changeset.manifest();
-	}
-
-	public List<Path> getAffectedFiles() {
-		// reports files as recorded in changelog. Note, merge revisions may have no
-		// files listed, and thus this method would return empty list, while
-		// #getModifiedFiles() would return list with merged file(s) (because it uses status to get 'em, not
-		// what #files() gives).
-		ArrayList<Path> rv = new ArrayList<Path>(changeset.files().size());
-		for (String name : changeset.files()) {
-			rv.add(pathHelper.path(name));
-		}
-		return rv;
-	}
-
-	public List<FileRevision> getModifiedFiles() {
-		if (modifiedFiles == null) {
-			initFileChanges();
-		}
-		return modifiedFiles;
-	}
-
-	public List<FileRevision> getAddedFiles() {
-		if (addedFiles == null) {
-			initFileChanges();
-		}
-		return addedFiles;
-	}
-
-	public List<Path> getRemovedFiles() {
-		if (deletedFiles == null) {
-			initFileChanges();
-		}
-		return deletedFiles;
-	}
-
-	public boolean isMerge() {
-		return !Nodeid.NULL.equals(getSecondParentRevision());
-	}
-	
-	public Nodeid getFirstParentRevision() {
-		// XXX may read once for both p1 and p2 
-		// or use ParentWalker to minimize reads even more.
-		byte[] p1 = new byte[20];
-		statusHelper.getRepo().getChangelog().parents(revNumber, new int[2], p1, null);
-		return Nodeid.fromBinary(p1, 0);
-	}
-	
-	public Nodeid getSecondParentRevision() {
-		byte[] p2 = new byte[20];
-		statusHelper.getRepo().getChangelog().parents(revNumber, new int[2], null, p2);
-		return Nodeid.fromBinary(p2, 0);
-	}
-
-	@Override
-	public Cset clone() {
-		try {
-			Cset copy = (Cset) super.clone();
-			copy.changeset = changeset.clone();
-			return copy;
-		} catch (CloneNotSupportedException ex) {
-			throw new InternalError(ex.toString());
-		}
-	}
-
-	private /*synchronized*/ void initFileChanges() {
-		ArrayList<Path> deleted = new ArrayList<Path>();
-		ArrayList<FileRevision> modified = new ArrayList<FileRevision>();
-		ArrayList<FileRevision> added = new ArrayList<FileRevision>();
-		HgStatusCollector.Record r = new HgStatusCollector.Record();
-		statusHelper.change(revNumber, r);
-		final HgRepository repo = statusHelper.getRepo();
-		for (Path s : r.getModified()) {
-			Nodeid nid = r.nodeidAfterChange(s);
-			if (nid == null) {
-				throw new IllegalArgumentException();
-			}
-			modified.add(new FileRevision(repo, nid, s));
-		}
-		for (Path s : r.getAdded()) {
-			Nodeid nid = r.nodeidAfterChange(s);
-			if (nid == null) {
-				throw new IllegalArgumentException();
-			}
-			added.add(new FileRevision(repo, nid, s));
-		}
-		for (Path s : r.getRemoved()) {
-			// with Path from getRemoved, may just copy
-			deleted.add(s);
-		}
-		modified.trimToSize();
-		added.trimToSize();
-		deleted.trimToSize();
-		modifiedFiles = Collections.unmodifiableList(modified);
-		addedFiles = Collections.unmodifiableList(added);
-		deletedFiles = Collections.unmodifiableList(deleted);
-	}
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgChangeset.java	Wed Feb 16 20:32:29 2011 +0100
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2011 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.core;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.tmatesoft.hg.core.LogCommand.FileRevision;
+import org.tmatesoft.hg.repo.HgChangelog.Changeset;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgStatusCollector;
+import org.tmatesoft.hg.util.PathPool;
+
+
+/**
+ * TODO rename to Changeset along with original Changeset moved to .repo and renamed to HgChangeset?
+ * Not thread-safe, don't try to read from different threads
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgChangeset implements Cloneable {
+	private final HgStatusCollector statusHelper;
+	private final PathPool pathHelper;
+
+	//
+	private Changeset changeset;
+	private Nodeid nodeid;
+
+	//
+	private List<FileRevision> modifiedFiles, addedFiles;
+	private List<Path> deletedFiles;
+	private int revNumber;
+
+	// XXX consider CommandContext with StatusCollector, PathPool etc. Commands optionally get CC through a cons or create new
+	// and pass it around
+	/*package-local*/HgChangeset(HgStatusCollector statusCollector, PathPool pathPool) {
+		statusHelper = statusCollector;
+		pathHelper = pathPool;
+	}
+	
+	/*package-local*/
+	void init(int localRevNumber, Nodeid nid, Changeset rawChangeset) {
+		revNumber = localRevNumber;
+		nodeid = nid;
+		changeset = rawChangeset;
+		modifiedFiles = addedFiles = null;
+		deletedFiles = null;
+	}
+	public int getRevision() {
+		return revNumber;
+	}
+	public Nodeid getNodeid() {
+		return nodeid;
+	}
+	public String getUser() {
+		return changeset.user();
+	}
+	public String getComment() {
+		return changeset.comment();
+	}
+	public String getBranch() {
+		return changeset.branch();
+	}
+	public String getDate() {
+		return changeset.dateString();
+	}
+	public Nodeid getManifestRevision() {
+		return changeset.manifest();
+	}
+
+	public List<Path> getAffectedFiles() {
+		// reports files as recorded in changelog. Note, merge revisions may have no
+		// files listed, and thus this method would return empty list, while
+		// #getModifiedFiles() would return list with merged file(s) (because it uses status to get 'em, not
+		// what #files() gives).
+		ArrayList<Path> rv = new ArrayList<Path>(changeset.files().size());
+		for (String name : changeset.files()) {
+			rv.add(pathHelper.path(name));
+		}
+		return rv;
+	}
+
+	public List<FileRevision> getModifiedFiles() {
+		if (modifiedFiles == null) {
+			initFileChanges();
+		}
+		return modifiedFiles;
+	}
+
+	public List<FileRevision> getAddedFiles() {
+		if (addedFiles == null) {
+			initFileChanges();
+		}
+		return addedFiles;
+	}
+
+	public List<Path> getRemovedFiles() {
+		if (deletedFiles == null) {
+			initFileChanges();
+		}
+		return deletedFiles;
+	}
+
+	public boolean isMerge() {
+		return !Nodeid.NULL.equals(getSecondParentRevision());
+	}
+	
+	public Nodeid getFirstParentRevision() {
+		// XXX may read once for both p1 and p2 
+		// or use ParentWalker to minimize reads even more.
+		byte[] p1 = new byte[20];
+		statusHelper.getRepo().getChangelog().parents(revNumber, new int[2], p1, null);
+		return Nodeid.fromBinary(p1, 0);
+	}
+	
+	public Nodeid getSecondParentRevision() {
+		byte[] p2 = new byte[20];
+		statusHelper.getRepo().getChangelog().parents(revNumber, new int[2], null, p2);
+		return Nodeid.fromBinary(p2, 0);
+	}
+
+	@Override
+	public HgChangeset clone() {
+		try {
+			HgChangeset copy = (HgChangeset) super.clone();
+			copy.changeset = changeset.clone();
+			return copy;
+		} catch (CloneNotSupportedException ex) {
+			throw new InternalError(ex.toString());
+		}
+	}
+
+	private /*synchronized*/ void initFileChanges() {
+		ArrayList<Path> deleted = new ArrayList<Path>();
+		ArrayList<FileRevision> modified = new ArrayList<FileRevision>();
+		ArrayList<FileRevision> added = new ArrayList<FileRevision>();
+		HgStatusCollector.Record r = new HgStatusCollector.Record();
+		statusHelper.change(revNumber, r);
+		final HgRepository repo = statusHelper.getRepo();
+		for (Path s : r.getModified()) {
+			Nodeid nid = r.nodeidAfterChange(s);
+			if (nid == null) {
+				throw new IllegalArgumentException();
+			}
+			modified.add(new FileRevision(repo, nid, s));
+		}
+		for (Path s : r.getAdded()) {
+			Nodeid nid = r.nodeidAfterChange(s);
+			if (nid == null) {
+				throw new IllegalArgumentException();
+			}
+			added.add(new FileRevision(repo, nid, s));
+		}
+		for (Path s : r.getRemoved()) {
+			// with Path from getRemoved, may just copy
+			deleted.add(s);
+		}
+		modified.trimToSize();
+		added.trimToSize();
+		deleted.trimToSize();
+		modifiedFiles = Collections.unmodifiableList(modified);
+		addedFiles = Collections.unmodifiableList(added);
+		deletedFiles = Collections.unmodifiableList(deleted);
+	}
+}
\ No newline at end of file
--- a/src/org/tmatesoft/hg/core/HgStatus.java	Wed Feb 16 20:13:41 2011 +0100
+++ b/src/org/tmatesoft/hg/core/HgStatus.java	Wed Feb 16 20:32:29 2011 +0100
@@ -19,7 +19,7 @@
 import java.util.Date;
 
 import org.tmatesoft.hg.internal.ChangelogHelper;
-import org.tmatesoft.hg.repo.Changeset;
+import org.tmatesoft.hg.repo.HgChangelog.Changeset;
 
 public class HgStatus {
 
--- a/src/org/tmatesoft/hg/core/LogCommand.java	Wed Feb 16 20:13:41 2011 +0100
+++ b/src/org/tmatesoft/hg/core/LogCommand.java	Wed Feb 16 20:32:29 2011 +0100
@@ -26,7 +26,8 @@
 import java.util.Set;
 import java.util.TreeSet;
 
-import org.tmatesoft.hg.repo.Changeset;
+import org.tmatesoft.hg.repo.HgChangelog.Changeset;
+import org.tmatesoft.hg.repo.HgChangelog;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgStatusCollector;
@@ -42,7 +43,7 @@
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
-public class LogCommand implements Changeset.Inspector {
+public class LogCommand implements HgChangelog.Inspector {
 
 	private final HgRepository repo;
 	private Set<String> users;
@@ -53,7 +54,7 @@
 	private Calendar date;
 	private Path file;
 	private boolean followHistory; // makes sense only when file != null
-	private Cset changeset;
+	private HgChangeset changeset;
 	
 	public LogCommand(HgRepository hgRepo) {
 		repo = hgRepo;
@@ -147,7 +148,7 @@
 	/**
 	 * Similar to {@link #execute(org.tmatesoft.hg.repo.Changeset.Inspector)}, collects and return result as a list.
 	 */
-	public List<Cset> execute() {
+	public List<HgChangeset> execute() {
 		CollectHandler collector = new CollectHandler();
 		execute(collector);
 		return collector.getChanges();
@@ -169,7 +170,7 @@
 		try {
 			delegate = handler;
 			count = 0;
-			changeset = new Cset(new HgStatusCollector(repo), new PathPool(repo.getPathHelper()));
+			changeset = new HgChangeset(new HgStatusCollector(repo), new PathPool(repo.getPathHelper()));
 			if (file == null) {
 				repo.getChangelog().range(startRev, endRev, this);
 			} else {
@@ -232,15 +233,15 @@
 
 	public interface Handler {
 		/**
-		 * @param changeset not necessarily a distinct instance each time, {@link Cset#clone() clone()} if need a copy.
+		 * @param changeset not necessarily a distinct instance each time, {@link HgChangeset#clone() clone()} if need a copy.
 		 */
-		void next(Cset changeset);
+		void next(HgChangeset changeset);
 	}
 	
 	/**
 	 * When {@link LogCommand} is executed against file, handler passed to {@link LogCommand#execute(Handler)} may optionally
 	 * implement this interface to get information about file renames. Method {@link #copy(FileRevision, FileRevision)} would
-	 * get invoked prior any changeset of the original file (if file history being followed) is reported via {@link #next(Cset)}.
+	 * get invoked prior any changeset of the original file (if file history being followed) is reported via {@link #next(HgChangeset)}.
 	 * 
 	 * For {@link LogCommand#file(Path, boolean)} with renamed file path and follow argument set to false, 
 	 * {@link #copy(FileRevision, FileRevision)} would be invoked for the first copy/rename in the history of the file, but not 
@@ -255,13 +256,13 @@
 	}
 	
 	public static class CollectHandler implements Handler {
-		private final List<Cset> result = new LinkedList<Cset>();
+		private final List<HgChangeset> result = new LinkedList<HgChangeset>();
 
-		public List<Cset> getChanges() {
+		public List<HgChangeset> getChanges() {
 			return Collections.unmodifiableList(result);
 		}
 
-		public void next(Cset changeset) {
+		public void next(HgChangeset changeset) {
 			result.add(changeset.clone());
 		}
 	}
--- a/src/org/tmatesoft/hg/internal/ChangelogHelper.java	Wed Feb 16 20:13:41 2011 +0100
+++ b/src/org/tmatesoft/hg/internal/ChangelogHelper.java	Wed Feb 16 20:32:29 2011 +0100
@@ -19,7 +19,7 @@
 import java.util.TreeMap;
 
 import org.tmatesoft.hg.core.Path;
-import org.tmatesoft.hg.repo.Changeset;
+import org.tmatesoft.hg.repo.HgChangelog.Changeset;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgInternals;
 import org.tmatesoft.hg.repo.HgRepository;
--- a/src/org/tmatesoft/hg/internal/KeywordFilter.java	Wed Feb 16 20:13:41 2011 +0100
+++ b/src/org/tmatesoft/hg/internal/KeywordFilter.java	Wed Feb 16 20:32:29 2011 +0100
@@ -22,7 +22,7 @@
 import java.util.TreeMap;
 
 import org.tmatesoft.hg.core.Path;
-import org.tmatesoft.hg.repo.Changeset;
+import org.tmatesoft.hg.repo.HgChangelog.Changeset;
 import org.tmatesoft.hg.repo.HgRepository;
 
 /**
--- a/src/org/tmatesoft/hg/repo/Changeset.java	Wed Feb 16 20:13:41 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,235 +0,0 @@
-/*
- * Copyright (c) 2010-2011 TMate Software Ltd
- *  
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * For information on how to redistribute this software under
- * the terms of a license other than GNU General Public License
- * contact TMate Software at support@hg4j.com
- */
-package org.tmatesoft.hg.repo;
-
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Formatter;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-import org.tmatesoft.hg.core.Nodeid;
-
-/**
- * @see mercurial/changelog.py:read()
- * <pre>
-        format used:
-        nodeid\n        : manifest node in ascii
-        user\n          : user, no \n or \r allowed
-        time tz extra\n : date (time is int or float, timezone is int)
-                        : extra is metadatas, encoded and separated by '\0'
-                        : older versions ignore it
-        files\n\n       : files modified by the cset, no \n or \r allowed
-        (.*)            : comment (free text, ideally utf-8)
-
-        changelog v0 doesn't use extra
- * </pre>
- *
- * @author Artem Tikhomirov
- * @author TMate Software Ltd.
- */
-public class Changeset implements Cloneable /*for those that would like to keep a copy*/ {
-	// TODO immutable
-	private /*final*/ Nodeid manifest;
-	private String user;
-	private String comment;
-	private List<String> files; // unmodifiable collection (otherwise #files() and implicit #clone() shall be revised)
-	private Date time;
-	private int timezone; // not sure it's of any use
-	private Map<String,String> extras;
-	
-	private Changeset() {
-	}
-	
-	public Nodeid manifest() {
-		return manifest;
-	}
-	
-	public String user() {
-		return user;
-	}
-	
-	public String comment() {
-		return comment;
-	}
-	
-	public List<String> files() {
-		return files;
-	}
-
-	public Date date() {
-		return time;
-	}
-	
-	public String dateString() {
-		StringBuilder sb = new StringBuilder(30);
-		Formatter f = new Formatter(sb, Locale.US);
-		f.format("%ta %<tb %<td %<tH:%<tM:%<tS %<tY %<tz", time);
-		return sb.toString();
-	}
-
-	public Map<String, String> extras() {
-		return extras;
-	}
-	
-	public String branch() {
-		return extras.get("branch");
-	}
-	
-	@Override
-	public String toString() {
-		StringBuilder sb = new StringBuilder();
-		sb.append("Changeset {");
-		sb.append("User: ").append(user).append(", ");
-		sb.append("Comment: ").append(comment).append(", ");
-		sb.append("Manifest: ").append(manifest).append(", ");
-		sb.append("Date: ").append(time).append(", ");
-		sb.append("Files: ").append(files.size());
-		for (String s : files) {
-			sb.append(", ").append(s);
-		}
-		if (extras != null) {
-			sb.append(", Extra: ").append(extras);
-		}
-		sb.append("}");
-		return sb.toString();
-	}
-	
-	@Override
-	public Changeset clone() {
-		try {
-			return (Changeset) super.clone();
-		} catch (CloneNotSupportedException ex) {
-			throw new InternalError(ex.toString());
-		}
-	}
-
-	public static Changeset parse(byte[] data, int offset, int length) {
-		Changeset rv = new Changeset();
-		rv.init(data, offset, length);
-		return rv;
-	}
-
-	/*package-local*/ void init(byte[] data, int offset, int length) {
-		final int bufferEndIndex = offset + length;
-		final byte lineBreak = (byte) '\n';
-		int breakIndex1 = indexOf(data, lineBreak, offset, bufferEndIndex);
-		if (breakIndex1 == -1) {
-			throw new IllegalArgumentException("Bad Changeset data");
-		}
-		Nodeid _nodeid = Nodeid.fromAscii(data, 0, breakIndex1);
-		int breakIndex2 = indexOf(data, lineBreak, breakIndex1+1, bufferEndIndex);
-		if (breakIndex2 == -1) {
-			throw new IllegalArgumentException("Bad Changeset data");
-		}
-		String _user = new String(data, breakIndex1+1, breakIndex2 - breakIndex1 - 1);
-		int breakIndex3 = indexOf(data, lineBreak, breakIndex2+1, bufferEndIndex);
-		if (breakIndex3 == -1) {
-			throw new IllegalArgumentException("Bad Changeset data");
-		}
-		String _timeString = new String(data, breakIndex2+1, breakIndex3 - breakIndex2 - 1);
-		int space1 = _timeString.indexOf(' ');
-		if (space1 == -1) {
-			throw new IllegalArgumentException("Bad Changeset data");
-		}
-		int space2 = _timeString.indexOf(' ', space1+1);
-		if (space2 == -1) {
-			space2 = _timeString.length();
-		}
-		long unixTime = Long.parseLong(_timeString.substring(0, space1)); // XXX Float, perhaps
-		int _timezone = Integer.parseInt(_timeString.substring(space1+1, space2));
-		// 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
-		// on commit and timezone is recorded to adjust it to UTC.
-		Date _time = new Date(unixTime * 1000);
-		String _extras = space2 < _timeString.length() ? _timeString.substring(space2+1) : null;
-		Map<String, String> _extrasMap;
-		if (_extras == null) {
-			 _extrasMap = Collections.singletonMap("branch", "default"); 
-		} else {
-			_extrasMap = new HashMap<String, String>();
-			for (String pair : _extras.split("\00")) {
-				int eq = pair.indexOf(':');
-				// FIXME need to decode key/value, @see changelog.py:decodeextra
-				_extrasMap.put(pair.substring(0, eq), pair.substring(eq+1));
-			}
-			if (!_extrasMap.containsKey("branch")) {
-				_extrasMap.put("branch", "default");
-			}
-			_extrasMap = Collections.unmodifiableMap(_extrasMap);
-		}
-		
-		//
-		int lastStart = breakIndex3 + 1;
-		int breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
-		ArrayList<String> _files = null;
-		if (breakIndex4 > lastStart) {
-			// if breakIndex4 == lastStart, we already found \n\n and hence there are no files (e.g. merge revision)
-			_files = new ArrayList<String>(5);
-			while (breakIndex4 != -1 && breakIndex4 + 1 < bufferEndIndex) {
-				_files.add(new String(data, lastStart, breakIndex4 - lastStart));
-				lastStart = breakIndex4 + 1;
-				if (data[breakIndex4 + 1] == lineBreak) {
-					// found \n\n
-					break;
-				} else {
-					breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
-				}
-			}
-			if (breakIndex4 == -1 || breakIndex4 >= bufferEndIndex) {
-				throw new IllegalArgumentException("Bad Changeset data");
-			}
-		} else {
-			breakIndex4--;
-		}
-		String _comment;
-		try {
-			_comment = new String(data, breakIndex4+2, bufferEndIndex - breakIndex4 - 2, "UTF-8");
-			// FIXME respect ui.fallbackencoding and try to decode if set 
-		} catch (UnsupportedEncodingException ex) {
-			_comment = "";
-			throw new IllegalStateException("Could hardly happen");
-		}
-		// change this instance at once, don't leave it partially changes in case of error
-		this.manifest = _nodeid;
-		this.user = _user;
-		this.time = _time;
-		this.timezone = _timezone;
-		this.files = _files == null ? Collections.<String>emptyList() : Collections.unmodifiableList(_files);
-		this.comment = _comment;
-		this.extras = _extrasMap;
-	}
-
-	private static int indexOf(byte[] src, byte what, int startOffset, int endIndex) {
-		for (int i = startOffset; i < endIndex; i++) {
-			if (src[i] == what) {
-				return i;
-			}
-		}
-		return -1;
-	}
-
-	public interface Inspector {
-		// first(), last(), single().
-		// <T>
-		// TODO describe whether cset is new instance each time
-		void next(int revisionNumber, Nodeid nodeid, Changeset cset);
-	}
-}
--- a/src/org/tmatesoft/hg/repo/HgBundle.java	Wed Feb 16 20:13:41 2011 +0100
+++ b/src/org/tmatesoft/hg/repo/HgBundle.java	Wed Feb 16 20:32:29 2011 +0100
@@ -26,6 +26,7 @@
 import org.tmatesoft.hg.internal.DataAccessProvider;
 import org.tmatesoft.hg.internal.DigestHelper;
 import org.tmatesoft.hg.internal.RevlogStream;
+import org.tmatesoft.hg.repo.HgChangelog.Changeset;
 
 
 /**
--- a/src/org/tmatesoft/hg/repo/HgChangelog.java	Wed Feb 16 20:13:41 2011 +0100
+++ b/src/org/tmatesoft/hg/repo/HgChangelog.java	Wed Feb 16 20:32:29 2011 +0100
@@ -16,9 +16,16 @@
  */
 package org.tmatesoft.hg.repo;
 
+import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Formatter;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
+import java.util.Map;
 
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.RevlogStream;
@@ -36,11 +43,11 @@
 		super(hgRepo, content);
 	}
 
-	public void all(final Changeset.Inspector inspector) {
+	public void all(final HgChangelog.Inspector inspector) {
 		range(0, content.revisionCount() - 1, inspector);
 	}
 
-	public void range(int start, int end, final Changeset.Inspector inspector) {
+	public void range(int start, int end, final HgChangelog.Inspector inspector) {
 		RevlogStream.Inspector i = new RevlogStream.Inspector() {
 			
 			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, byte[] data) {
@@ -65,7 +72,7 @@
 		return rv; 
 	}
 
-	public void range(final Changeset.Inspector inspector, final int... revisions) {
+	public void range(final HgChangelog.Inspector inspector, final int... revisions) {
 		if (revisions == null || revisions.length == 0) {
 			return;
 		}
@@ -81,4 +88,212 @@
 		Arrays.sort(revisions);
 		content.iterate(revisions[0], revisions[revisions.length - 1], true, i);
 	}
+
+	
+	public interface Inspector {
+		// TODO describe whether cset is new instance each time
+		void next(int revisionNumber, Nodeid nodeid, Changeset cset);
+	}
+
+
+	/**
+	 * Entry in the Changelog
+	 */
+	public static class Changeset implements Cloneable /* for those that would like to keep a copy */{
+		// TODO immutable
+		private/* final */Nodeid manifest;
+			private String user;
+			private String comment;
+			private List<String> files; // unmodifiable collection (otherwise #files() and implicit #clone() shall be revised)
+			private Date time;
+			private int timezone; // not sure it's of any use
+			private Map<String, String> extras;
+
+			/**
+			 * @see mercurial/changelog.py:read()
+			 * 
+			 * <pre>
+			 *         format used:
+				 *         nodeid\n        : manifest node in ascii
+				 *         user\n          : user, no \n or \r allowed
+				 *         time tz extra\n : date (time is int or float, timezone is int)
+				 *                         : extra is metadatas, encoded and separated by '\0'
+				 *                         : older versions ignore it
+				 *         files\n\n       : files modified by the cset, no \n or \r allowed
+				 *         (.*)            : comment (free text, ideally utf-8)
+				 * 
+				 *         changelog v0 doesn't use extra
+				 * </pre>
+				 */
+					private Changeset() {
+					}
+
+					public Nodeid manifest() {
+						return manifest;
+					}
+
+					public String user() {
+						return user;
+					}
+
+					public String comment() {
+						return comment;
+					}
+
+					public List<String> files() {
+						return files;
+					}
+
+					public Date date() {
+						return time;
+					}
+
+					public String dateString() {
+						StringBuilder sb = new StringBuilder(30);
+						Formatter f = new Formatter(sb, Locale.US);
+						f.format("%ta %<tb %<td %<tH:%<tM:%<tS %<tY %<tz", time);
+						return sb.toString();
+					}
+
+					public Map<String, String> extras() {
+						return extras;
+					}
+
+					public String branch() {
+						return extras.get("branch");
+					}
+
+					@Override
+					public String toString() {
+						StringBuilder sb = new StringBuilder();
+						sb.append("Changeset {");
+						sb.append("User: ").append(user).append(", ");
+						sb.append("Comment: ").append(comment).append(", ");
+						sb.append("Manifest: ").append(manifest).append(", ");
+						sb.append("Date: ").append(time).append(", ");
+						sb.append("Files: ").append(files.size());
+						for (String s : files) {
+							sb.append(", ").append(s);
+						}
+						if (extras != null) {
+							sb.append(", Extra: ").append(extras);
+						}
+						sb.append("}");
+						return sb.toString();
+					}
+
+					@Override
+					public Changeset clone() {
+						try {
+							return (Changeset) super.clone();
+						} catch (CloneNotSupportedException ex) {
+							throw new InternalError(ex.toString());
+						}
+					}
+
+					public static Changeset parse(byte[] data, int offset, int length) {
+						Changeset rv = new Changeset();
+						rv.init(data, offset, length);
+						return rv;
+					}
+
+					/* package-local */void init(byte[] data, int offset, int length) {
+						final int bufferEndIndex = offset + length;
+						final byte lineBreak = (byte) '\n';
+						int breakIndex1 = indexOf(data, lineBreak, offset, bufferEndIndex);
+						if (breakIndex1 == -1) {
+							throw new IllegalArgumentException("Bad Changeset data");
+						}
+						Nodeid _nodeid = Nodeid.fromAscii(data, 0, breakIndex1);
+						int breakIndex2 = indexOf(data, lineBreak, breakIndex1 + 1, bufferEndIndex);
+						if (breakIndex2 == -1) {
+							throw new IllegalArgumentException("Bad Changeset data");
+						}
+						String _user = new String(data, breakIndex1 + 1, breakIndex2 - breakIndex1 - 1);
+						int breakIndex3 = indexOf(data, lineBreak, breakIndex2 + 1, bufferEndIndex);
+						if (breakIndex3 == -1) {
+							throw new IllegalArgumentException("Bad Changeset data");
+						}
+						String _timeString = new String(data, breakIndex2 + 1, breakIndex3 - breakIndex2 - 1);
+						int space1 = _timeString.indexOf(' ');
+						if (space1 == -1) {
+							throw new IllegalArgumentException("Bad Changeset data");
+						}
+						int space2 = _timeString.indexOf(' ', space1 + 1);
+						if (space2 == -1) {
+							space2 = _timeString.length();
+						}
+						long unixTime = Long.parseLong(_timeString.substring(0, space1)); // XXX Float, perhaps
+						int _timezone = Integer.parseInt(_timeString.substring(space1 + 1, space2));
+						// 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
+						// on commit and timezone is recorded to adjust it to UTC.
+						Date _time = new Date(unixTime * 1000);
+								String _extras = space2 < _timeString.length() ? _timeString.substring(space2 + 1) : null;
+								Map<String, String> _extrasMap;
+								if (_extras == null) {
+									_extrasMap = Collections.singletonMap("branch", "default");
+								} else {
+									_extrasMap = new HashMap<String, String>();
+									for (String pair : _extras.split("\00")) {
+										int eq = pair.indexOf(':');
+											// FIXME need to decode key/value, @see changelog.py:decodeextra
+											_extrasMap.put(pair.substring(0, eq), pair.substring(eq + 1));
+									}
+									if (!_extrasMap.containsKey("branch")) {
+										_extrasMap.put("branch", "default");
+									}
+									_extrasMap = Collections.unmodifiableMap(_extrasMap);
+								}
+
+								//
+								int lastStart = breakIndex3 + 1;
+								int breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
+								ArrayList<String> _files = null;
+								if (breakIndex4 > lastStart) {
+									// if breakIndex4 == lastStart, we already found \n\n and hence there are no files (e.g. merge revision)
+									_files = new ArrayList<String>(5);
+									while (breakIndex4 != -1 && breakIndex4 + 1 < bufferEndIndex) {
+										_files.add(new String(data, lastStart, breakIndex4 - lastStart));
+										lastStart = breakIndex4 + 1;
+										if (data[breakIndex4 + 1] == lineBreak) {
+											// found \n\n
+											break;
+										} else {
+											breakIndex4 = indexOf(data, lineBreak, lastStart, bufferEndIndex);
+										}
+									}
+									if (breakIndex4 == -1 || breakIndex4 >= bufferEndIndex) {
+										throw new IllegalArgumentException("Bad Changeset data");
+									}
+								} else {
+									breakIndex4--;
+								}
+								String _comment;
+								try {
+									_comment = new String(data, breakIndex4 + 2, bufferEndIndex - breakIndex4 - 2, "UTF-8");
+									// FIXME respect ui.fallbackencoding and try to decode if set
+								} catch (UnsupportedEncodingException ex) {
+									_comment = "";
+									throw new IllegalStateException("Could hardly happen");
+								}
+								// change this instance at once, don't leave it partially changes in case of error
+								this.manifest = _nodeid;
+								this.user = _user;
+								this.time = _time;
+								this.timezone = _timezone;
+								this.files = _files == null ? Collections.<String> emptyList() : Collections.unmodifiableList(_files);
+								this.comment = _comment;
+								this.extras = _extrasMap;
+					}
+
+					private static int indexOf(byte[] src, byte what, int startOffset, int endIndex) {
+						for (int i = startOffset; i < endIndex; i++) {
+							if (src[i] == what) {
+								return i;
+							}
+						}
+						return -1;
+					}
+	}
+
 }
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java	Wed Feb 16 20:13:41 2011 +0100
+++ b/src/org/tmatesoft/hg/repo/HgDataFile.java	Wed Feb 16 20:32:29 2011 +0100
@@ -147,11 +147,11 @@
 		return rv;
 	}
 
-	public void history(Changeset.Inspector inspector) {
+	public void history(HgChangelog.Inspector inspector) {
 		history(0, content.revisionCount() - 1, inspector);
 	}
 
-	public void history(int start, int end, Changeset.Inspector inspector) {
+	public void history(int start, int end, HgChangelog.Inspector inspector) {
 		if (!exists()) {
 			throw new IllegalStateException("Can't get history of invalid repository file node"); 
 		}
--- a/test/org/tmatesoft/hg/test/TestHistory.java	Wed Feb 16 20:13:41 2011 +0100
+++ b/test/org/tmatesoft/hg/test/TestHistory.java	Wed Feb 16 20:32:29 2011 +0100
@@ -28,7 +28,7 @@
 import org.hamcrest.CoreMatchers;
 import org.junit.Rule;
 import org.junit.Test;
-import org.tmatesoft.hg.core.Cset;
+import org.tmatesoft.hg.core.HgChangeset;
 import org.tmatesoft.hg.core.LogCommand;
 import org.tmatesoft.hg.core.LogCommand.CollectHandler;
 import org.tmatesoft.hg.core.LogCommand.FileHistoryHandler;
@@ -74,7 +74,7 @@
 	public void testCompleteLog() throws Exception {
 		changelogParser.reset();
 		eh.run("hg", "log", "--debug");
-		List<Cset> r = new LogCommand(repo).execute();
+		List<HgChangeset> r = new LogCommand(repo).execute();
 		report("hg log - COMPLETE REPO HISTORY", r, true); 
 	}
 	
@@ -103,9 +103,9 @@
 				// cmdline always gives in changesets in order from newest (bigger rev number) to oldest.
 				// LogCommand does other way round, from oldest to newest, follewed by revisions of copy source, if any
 				// (apparently older than oldest of the copy target). Hence need to sort Java results according to rev numbers
-				final LinkedList<Cset> sorted = new LinkedList<Cset>(h.getChanges());
-				Collections.sort(sorted, new Comparator<Cset>() {
-					public int compare(Cset cs1, Cset cs2) {
+				final LinkedList<HgChangeset> sorted = new LinkedList<HgChangeset>(h.getChanges());
+				Collections.sort(sorted, new Comparator<HgChangeset>() {
+					public int compare(HgChangeset cs1, HgChangeset cs2) {
 						return cs1.getRevision() < cs2.getRevision() ? 1 : -1;
 					}
 				});
@@ -116,13 +116,13 @@
 		}
 	}
 
-	private void report(String what, List<Cset> r, boolean reverseConsoleResults) {
+	private void report(String what, List<HgChangeset> r, boolean reverseConsoleResults) {
 		final List<Record> consoleResult = changelogParser.getResult();
 		if (reverseConsoleResults) {
 			Collections.reverse(consoleResult);
 		}
 		Iterator<LogOutputParser.Record> consoleResultItr = consoleResult.iterator();
-		for (Cset cs : r) {
+		for (HgChangeset cs : r) {
 			LogOutputParser.Record cr = consoleResultItr.next();
 			int x = cs.getRevision() == cr.changesetIndex ? 0x1 : 0;
 			x |= cs.getDate().equals(cr.date) ? 0x2 : 0;