changeset 232:b7347daa50e3

Allow to cat a file with changeset revision
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 02 Jun 2011 05:13:39 +0200
parents 1792b37650f2
children 1d389c0cb0a5
files cmdline/org/tmatesoft/hg/console/Main.java src/org/tmatesoft/hg/core/HgCatCommand.java src/org/tmatesoft/hg/repo/HgManifest.java
diffstat 3 files changed, 107 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Wed Jun 01 05:44:25 2011 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Main.java	Thu Jun 02 05:13:39 2011 +0200
@@ -24,6 +24,7 @@
 import java.util.Map;
 
 import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
+import org.tmatesoft.hg.core.HgCatCommand;
 import org.tmatesoft.hg.core.HgFileRevision;
 import org.tmatesoft.hg.core.HgManifestCommand;
 import org.tmatesoft.hg.core.Nodeid;
@@ -64,7 +65,8 @@
 
 	public static void main(String[] args) throws Exception {
 		Main m = new Main(args);
-		m.testMergeState();
+		m.testCatAtCsetRevision();
+//		m.testMergeState();
 //		m.testFileStatus();
 //		m.dumpBranches();
 //		m.inflaterLengthException();
@@ -77,6 +79,16 @@
 //		m.bunchOfTests();
 	}
 	
+	// TODO as test in TestCat
+	private void testCatAtCsetRevision() throws Exception {
+		HgCatCommand cmd = new HgCatCommand(hgRepo);
+		cmd.file(Path.create("src/org/tmatesoft/hg/internal/RevlogStream.java"));
+		cmd.changeset(Nodeid.fromAscii("08db726a0fb7914ac9d27ba26dc8bbf6385a0554"));
+		final ByteArrayChannel sink = new ByteArrayChannel();
+		cmd.execute(sink);
+		System.out.println(sink.toArray().length);
+	}
+	
 	private void testMergeState() throws Exception {
 		final HgMergeState mergeState = hgRepo.getMergeState();
 		mergeState.refresh();
--- a/src/org/tmatesoft/hg/core/HgCatCommand.java	Wed Jun 01 05:44:25 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgCatCommand.java	Thu Jun 02 05:13:39 2011 +0200
@@ -41,6 +41,7 @@
 	private Path file;
 	private int localRevision = TIP;
 	private Nodeid revision;
+	private Nodeid cset;
 
 	public HgCatCommand(HgRepository hgRepo) {
 		repo = hgRepo;
@@ -61,7 +62,11 @@
 	}
 
 	/**
+	 * Select specific local revision of the file to cat. Note, revision numbering is of particular file, not that of
+	 * repository (i.e. revision 0 means initial content of the file, irrespective of changeset revision at the time of commit) 
+	 * 
 	 * Invocation of this method clears revision set with {@link #revision(Nodeid)} or {@link #revision(int)} earlier.
+	 * 
 	 * XXX rev can't be WORKING_COPY (if allowed, need to implement in #execute())
 	 * @param rev local revision number, non-negative, or one of predefined constants. Note, use of {@link HgRepository#BAD_REVISION}, 
 	 * although possible, makes little sense (command would fail if executed).  
@@ -73,13 +78,16 @@
 		}
 		localRevision = rev;
 		revision = null;
+		cset = null;
 		return this;
 	}
 	
 	/**
-	 * Select revision to read. Invocation of this method clears revision set with {@link #revision(int)} or {@link #revision(Nodeid)} earlier.
+	 * Select revision to read. Note, this revision is file revision (i.e. the one from manifest), not the changeset revision.
+	 *  
+	 * Invocation of this method clears revision set with {@link #revision(int)} or {@link #revision(Nodeid)} earlier.
 	 * 
-	 * @param nodeid - unique revision identifier, Note, use of <code>null</code> or {@link Nodeid#NULL} is senseless
+	 * @param nodeid - unique file revision identifier, Note, use of <code>null</code> or {@link Nodeid#NULL} is senseless
 	 * @return <code>this</code> for convenience
 	 */
 	public HgCatCommand revision(Nodeid nodeid) {
@@ -88,6 +96,23 @@
 		}
 		revision = nodeid;
 		localRevision = BAD_REVISION;
+		cset = null;
+		return this;
+	}
+	
+	/**
+	 * Select whatever revision of the file that was actual at the time of the specified changeset. Unlike {@link #revision(int)} or {@link #revision(Nodeid)}, this method 
+	 * operates in terms of repository global revisions (aka changesets). 
+	 * 
+	 * Invocation of this method clears local file revisions selection.
+	 * 
+	 * @param nodeid changeset revision
+	 * @return <code>this</code> for convenience
+	 */
+	public HgCatCommand changeset(Nodeid nodeid) {
+		localRevision = BAD_REVISION;
+		revision = null;
+		cset = nodeid;
 		return this;
 	}
 
@@ -99,8 +124,8 @@
 	 * @throws IllegalArgumentException when command arguments are incomplete or wrong
 	 */
 	public void execute(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException {
-		if (localRevision == BAD_REVISION && revision == null) {
-			throw new IllegalArgumentException("Either local file revision number or nodeid shall be specified");
+		if (localRevision == BAD_REVISION && revision == null && cset == null) {
+			throw new IllegalArgumentException("File revision, corresponing local number, or a changset nodeid shall be specified");
 		}
 		if (file == null) {
 			throw new IllegalArgumentException("Name of the file is missing");
@@ -113,7 +138,25 @@
 			throw new HgDataStreamException(file, new FileNotFoundException(file.toString()));
 		}
 		int revToExtract;
-		if (revision != null) {
+		if (cset != null) {
+			int csetRev = repo.getChangelog().getLocalRevision(cset);
+			Nodeid toExtract = null;
+			do {
+				toExtract = repo.getManifest().getFileRevision(csetRev, file);
+				if (toExtract == null) {
+					if (dataFile.isCopy()) {
+						file = dataFile.getCopySourceName();
+						dataFile = repo.getFileNode(file);
+					} else {
+						break;
+					}
+				}
+			} while (toExtract == null);
+			if (toExtract == null) {
+				throw new HgBadStateException(String.format("File %s not its origins were not known at repository %s revision", file, cset.shortNotation()));
+			}
+			revToExtract = dataFile.getLocalRevision(toExtract);
+		} else if (revision != null) {
 			revToExtract = dataFile.getLocalRevision(revision);
 		} else {
 			revToExtract = localRevision;
--- a/src/org/tmatesoft/hg/repo/HgManifest.java	Wed Jun 01 05:44:25 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgManifest.java	Thu Jun 02 05:13:39 2011 +0200
@@ -18,6 +18,7 @@
 
 import static org.tmatesoft.hg.repo.HgRepository.TIP;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -25,9 +26,11 @@
 import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.DataAccess;
+import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.Lifecycle;
 import org.tmatesoft.hg.internal.Pool;
 import org.tmatesoft.hg.internal.RevlogStream;
+import org.tmatesoft.hg.util.Path;
 
 
 /**
@@ -57,6 +60,7 @@
 		content.iterate(start0, end0, true, new ManifestParser(inspector));
 	}
 	
+	// manifest revision number that corresponds to the given changeset
 	/*package-local*/ int fromChangelog(int revisionNumber) {
 		if (HgInternals.wrongLocalRevision(revisionNumber)) {
 			throw new IllegalArgumentException(String.valueOf(revisionNumber));
@@ -68,6 +72,48 @@
 		return revisionMap.at(revisionNumber);
 	}
 	
+	/**
+	 * Extracts file revision as it was known at the time of given changeset.
+	 * 
+	 * @param revisionNumber local changeset index 
+	 * @param file path to file in question
+	 * @return file revision or <code>null</code> if manifest at specified revision doesn't list such file
+	 */
+	@Experimental(reason="Perhaps, HgDataFile shall own this method")
+	public Nodeid getFileRevision(int revisionNumber, final Path file) {
+		int rev = fromChangelog(revisionNumber);
+		final Nodeid[] rv = new Nodeid[] { null };
+		content.iterate(rev, rev, true, new RevlogStream.Inspector() {
+			
+			public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) {
+				ByteArrayOutputStream bos = new ByteArrayOutputStream();
+				try {
+					byte b;
+					while (!data.isEmpty() && (b = data.readByte()) != '\n') {
+						if (b != 0) {
+							bos.write(b);
+						} else {
+							String fname = new String(bos.toByteArray());
+							bos.reset();
+							if (file.toString().equals(fname)) {
+								byte[] nid = new byte[40];  
+								data.readBytes(nid, 0, 40);
+								rv[0] = Nodeid.fromAscii(nid, 0, 40);
+								break;
+							}
+							// else skip to the end of line
+							while (!data.isEmpty() && (b = data.readByte()) != '\n')
+								;
+						}
+					}
+				} catch (IOException ex) {
+					throw new HgBadStateException(ex);
+				}
+			}
+		});
+		return rv[0];
+	}
+			
 	public interface Inspector {
 		boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision);
 		boolean next(Nodeid nid, String fname, String flags);