diff cmdline/org/tmatesoft/hg/console/Clone.java @ 186:44a34baabea0

Clone refactored into a command. HgBundle needs means to control its lifecycle, to be deleted when no longer needed
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 14 Apr 2011 00:47:04 +0200
parents 2c3e96674e2a
children 45dc79e545f5
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Clone.java	Wed Apr 13 19:55:59 2011 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Clone.java	Thu Apr 14 00:47:04 2011 +0200
@@ -16,308 +16,42 @@
  */
 package org.tmatesoft.hg.console;
 
-import static org.tmatesoft.hg.core.Nodeid.NULL;
-import static org.tmatesoft.hg.internal.RequiresFile.*;
-import static org.tmatesoft.hg.internal.RequiresFile.DOTENCODE;
-import static org.tmatesoft.hg.internal.RequiresFile.FNCACHE;
-
-import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.net.URL;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.TreeMap;
-import java.util.zip.DeflaterOutputStream;
 
-import org.tmatesoft.hg.core.HgBadStateException;
-import org.tmatesoft.hg.core.HgRepoFacade;
-import org.tmatesoft.hg.core.Nodeid;
-import org.tmatesoft.hg.internal.ByteArrayDataAccess;
-import org.tmatesoft.hg.internal.DataAccess;
-import org.tmatesoft.hg.internal.DigestHelper;
-import org.tmatesoft.hg.internal.Internals;
-import org.tmatesoft.hg.internal.RequiresFile;
-import org.tmatesoft.hg.internal.RevlogStream;
-import org.tmatesoft.hg.repo.HgBundle;
+import org.tmatesoft.hg.core.HgCloneCommand;
 import org.tmatesoft.hg.repo.HgLookup;
 import org.tmatesoft.hg.repo.HgRemoteRepository;
-import org.tmatesoft.hg.repo.HgBundle.GroupElement;
-import org.tmatesoft.hg.repo.HgRepository;
-import org.tmatesoft.hg.util.PathRewrite;
 
 /**
- * WORK IN PROGRESS, DO NOT USE
+ * Initial clone of a repository. Creates a brand new repository and populates it from specified source. 
  * 
  * @author Artem Tikhomirov
  * @author TMate Software Ltd.
  */
 public class Clone {
-/*
- * Changegroup: 
- * http://mercurial.selenic.com/wiki/Merge 
- * http://mercurial.selenic.com/wiki/WireProtocol 
- * 
- * according to latter, bundleformat data is sent through zlib
- * (there's no header like HG10?? with the server output, though, 
- * as one may expect according to http://mercurial.selenic.com/wiki/BundleFormat)
- */
+
+	// ran with args: svnkit c:\temp\hg\test-clone
 	public static void main(String[] args) throws Exception {
 		Options cmdLineOpts = Options.parse(args);
-		HgRepoFacade hgRepo = new HgRepoFacade();
-		if (!hgRepo.init(cmdLineOpts.findRepository())) {
-			System.err.printf("Can't find repository in: %s\n", hgRepo.getRepository().getLocation());
+		List<String> noOptsArgs = cmdLineOpts.getList("");
+		if (noOptsArgs.isEmpty()) {
+			System.err.println("Need at least one argument pointing to remote server to pull changes from");
 			return;
 		}
-		File destDir = new File("/temp/hg/clone-01/");
-		if (destDir.exists()) {
-			if (!destDir.isDirectory()) {
-				throw new IllegalArgumentException();
-			} else if (destDir.list().length > 0) {
-				throw new IllegalArgumentException();
-			}
-		} else {
-			destDir.mkdirs();
-		}
-		// if cloning remote repo, which can stream and no revision is specified -
-		// can use 'stream_out' wireproto
-		//
-		// //////// 1. from Remote.java take code that asks changegroup from remote server and write it down to temp file
-		// //////// 2. then, read the file with HgBundle
-		// //////// 3. process changelog, memorize nodeids to index
-		// //////// 4. process manifest, using map from step 3, collect manifest nodeids
-		// //////// 5. process every file, using map from 3, and consult set from step 4 to ensure repo is correct
-		// access source
-		HgRemoteRepository remoteRepo = new HgLookup().detect(new URL("https://asd/hg/"));
-		// discover changes
-		HgBundle completeChanges = remoteRepo.getChanges(Collections.singletonList(NULL));
-		WriteDownMate mate = new WriteDownMate(destDir);
-		// instantiate new repo in the destdir
-		mate.initEmptyRepository();
-		// pull changes
-		completeChanges.inspectAll(mate);
-		mate.complete();
-		// completeChanges.unlink();
-	}
-
-	private static class WriteDownMate implements HgBundle.Inspector {
-		private final File hgDir;
-		private FileOutputStream indexFile;
-		private final PathRewrite storagePathHelper;
-
-		private final TreeMap<Nodeid, Integer> changelogIndexes = new TreeMap<Nodeid, Integer>();
-		private boolean collectChangelogIndexes = false;
-
-		private int base = -1;
-		private long offset = 0;
-		private DataAccess prevRevContent;
-		private final DigestHelper dh = new DigestHelper();
-		private final ArrayList<Nodeid> revisionSequence = new ArrayList<Nodeid>(); // last visited nodes first
-
-		private final LinkedList<String> fncacheFiles = new LinkedList<String>();
-
-		public WriteDownMate(File destDir) {
-			hgDir = new File(destDir, ".hg");
-			Internals i = new Internals();
-			i.setStorageConfig(1, STORE | FNCACHE | DOTENCODE);
-			storagePathHelper = i.buildDataFilesHelper();
-		}
-
-		public void initEmptyRepository() throws IOException {
-			hgDir.mkdir();
-			FileOutputStream requiresFile = new FileOutputStream(new File(hgDir, "requires"));
-			requiresFile.write("revlogv1\nstore\nfncache\ndotencode\n".getBytes());
-			requiresFile.close();
-			new File(hgDir, "store").mkdir(); // with that, hg verify says ok.
-		}
-
-		public void complete() throws IOException {
-			FileOutputStream fncacheFile = new FileOutputStream(new File(hgDir, "store/fncache"));
-			for (String s : fncacheFiles) {
-				fncacheFile.write(s.getBytes());
-				fncacheFile.write(0x0A); // http://mercurial.selenic.com/wiki/fncacheRepoFormat
-			}
-			fncacheFile.close();
-		}
-
-		public void changelogStart() {
-			try {
-				base = -1;
-				offset = 0;
-				revisionSequence.clear();
-				indexFile = new FileOutputStream(new File(hgDir, "store/00changelog.i"));
-				collectChangelogIndexes = true;
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-
-		public void changelogEnd() {
-			try {
-				if (prevRevContent != null) {
-					prevRevContent.done();
-					prevRevContent = null;
-				}
-				collectChangelogIndexes = false;
-				indexFile.close();
-				indexFile = null;
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-
-		public void manifestStart() {
-			try {
-				base = -1;
-				offset = 0;
-				revisionSequence.clear();
-				indexFile = new FileOutputStream(new File(hgDir, "store/00manifest.i"));
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-
-		public void manifestEnd() {
-			try {
-				if (prevRevContent != null) {
-					prevRevContent.done();
-					prevRevContent = null;
-				}
-				indexFile.close();
-				indexFile = null;
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
+		HgCloneCommand cmd = new HgCloneCommand();
+		String remoteRepo = noOptsArgs.get(0);
+		HgRemoteRepository hgRemote = new HgLookup().detectRemote(remoteRepo, null);
+		if (hgRemote.isInvalid()) {
+			System.err.printf("Remote repository %s is not valid", hgRemote.getLocation());
+			return;
 		}
-		
-		public void fileStart(String name) {
-			try {
-				base = -1;
-				offset = 0;
-				revisionSequence.clear();
-				fncacheFiles.add("data/" + name + ".i"); // FIXME this is pure guess, 
-				// need to investigate more how filenames are kept in fncache
-				File file = new File(hgDir, storagePathHelper.rewrite(name));
-				file.getParentFile().mkdirs();
-				indexFile = new FileOutputStream(file);
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-
-		public void fileEnd(String name) {
-			try {
-				if (prevRevContent != null) {
-					prevRevContent.done();
-					prevRevContent = null;
-				}
-				indexFile.close();
-				indexFile = null;
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-		}
-
-		private int knownRevision(Nodeid p) {
-			if (NULL.equals(p)) {
-				return -1;
-			} else {
-				for (int i = revisionSequence.size() - 1; i >= 0; i--) {
-					if (revisionSequence.get(i).equals(p)) {
-						return i;
-					}
-				}
-			}
-			throw new HgBadStateException(String.format("Can't find index of %s", p.shortNotation()));
+		cmd.source(hgRemote);
+		if (noOptsArgs.size() > 1) {
+			cmd.destination(new File(noOptsArgs.get(1)));
+		} else {
+			cmd.destination(new File(System.getProperty("user.dir")));
 		}
-
-		public boolean element(GroupElement ge) {
-			try {
-				assert indexFile != null;
-				boolean writeComplete = false;
-				Nodeid p1 = ge.firstParent();
-				Nodeid p2 = ge.secondParent();
-				if (NULL.equals(p1) && NULL.equals(p2) /* or forced flag, does REVIDX_PUNCHED_FLAG indicate that? */) {
-					prevRevContent = new ByteArrayDataAccess(new byte[0]);
-					writeComplete = true;
-				}
-				byte[] content = ge.apply(prevRevContent);
-				byte[] calculated = dh.sha1(p1, p2, content).asBinary();
-				final Nodeid node = ge.node();
-				if (!node.equalsTo(calculated)) {
-					throw new HgBadStateException("Checksum failed");
-				}
-				final int link;
-				if (collectChangelogIndexes) {
-					changelogIndexes.put(node, revisionSequence.size());
-					link = revisionSequence.size();
-				} else {
-					Integer csRev = changelogIndexes.get(ge.cset());
-					if (csRev == null) {
-						throw new HgBadStateException(String.format("Changelog doesn't contain revision %s", ge.cset().shortNotation()));
-					}
-					link = csRev.intValue();
-				}
-				final int p1Rev = knownRevision(p1), p2Rev = knownRevision(p2);
-				DataAccess patchContent = ge.rawData();
-				writeComplete = writeComplete || patchContent.length() >= (/* 3/4 of actual */content.length - (content.length >>> 2));
-				if (writeComplete) {
-					base = revisionSequence.size();
-				}
-				final byte[] sourceData = writeComplete ? content : patchContent.byteArray();
-				final byte[] data;
-				ByteArrayOutputStream bos = new ByteArrayOutputStream(content.length);
-				DeflaterOutputStream dos = new DeflaterOutputStream(bos);
-				dos.write(sourceData);
-				dos.close();
-				final byte[] compressedData = bos.toByteArray();
-				dos = null;
-				bos = null;
-				final Byte dataPrefix;
-				if (compressedData.length >= (sourceData.length - (sourceData.length >>> 2))) {
-					// compression wasn't too effective,
-					data = sourceData;
-					dataPrefix = 'u';
-				} else {
-					data = compressedData;
-					dataPrefix = null;
-				}
-
-				ByteBuffer header = ByteBuffer.allocate(64 /* REVLOGV1_RECORD_SIZE */);
-				if (offset == 0) {
-					final int INLINEDATA = 1 << 16;
-					header.putInt(1 /* RevlogNG */ | INLINEDATA);
-					header.putInt(0);
-				} else {
-					header.putLong(offset << 16);
-				}
-				final int compressedLen = data.length + (dataPrefix == null ? 0 : 1);
-				header.putInt(compressedLen);
-				header.putInt(content.length);
-				header.putInt(base);
-				header.putInt(link);
-				header.putInt(p1Rev);
-				header.putInt(p2Rev);
-				header.put(node.toByteArray());
-				// assume 12 bytes left are zeros
-				indexFile.write(header.array());
-				if (dataPrefix != null) {
-					indexFile.write(dataPrefix.byteValue());
-				}
-				indexFile.write(data);
-				//
-				offset += compressedLen;
-				revisionSequence.add(node);
-				prevRevContent.done();
-				prevRevContent = new ByteArrayDataAccess(content);
-			} catch (IOException ex) {
-				throw new HgBadStateException(ex);
-			}
-			return true;
-		}
+		cmd.execute();
 	}
 }