changeset 529:95bdcf75e71e

Command to schedule addition/removal of repository files
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 21 Jan 2013 19:41:51 +0100
parents f7fbf48b9383
children 0f6fa88e2162
files build.xml src/org/tmatesoft/hg/core/HgAddRemoveCommand.java src/org/tmatesoft/hg/internal/DirstateBuilder.java test/org/tmatesoft/hg/test/TestAddRemove.java test/org/tmatesoft/hg/test/TestRevert.java
diffstat 5 files changed, 249 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/build.xml	Thu Jan 17 19:23:52 2013 +0100
+++ b/build.xml	Mon Jan 21 19:41:51 2013 +0100
@@ -103,6 +103,7 @@
 			<test name="org.tmatesoft.hg.test.TestCatCommand" />
 			<test name="org.tmatesoft.hg.test.TestRevert" />
 			<test name="org.tmatesoft.hg.test.TestCheckout" />
+			<test name="org.tmatesoft.hg.test.TestAddRemove" />
 		</junit>
 	</target>
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgAddRemoveCommand.java	Mon Jan 21 19:41:51 2013 +0100
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2013 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.LinkedHashSet;
+
+import org.tmatesoft.hg.internal.DirstateBuilder;
+import org.tmatesoft.hg.internal.DirstateReader;
+import org.tmatesoft.hg.internal.Experimental;
+import org.tmatesoft.hg.internal.Internals;
+import org.tmatesoft.hg.repo.HgManifest.Flags;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.repo.HgRuntimeException;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * WORK IN PROGRESS
+ * 
+ * Schedule files for addition and removal 
+ * XXX and, perhaps, forget() functionality shall be here as well?
+ * 
+ * @since 1.1
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+@Experimental(reason="Work in progress")
+public class HgAddRemoveCommand extends HgAbstractCommand<HgAddRemoveCommand> {
+	
+	private final HgRepository repo;
+	private final LinkedHashSet<Path> toAdd, toRemove;
+
+	public HgAddRemoveCommand(HgRepository hgRepo) {
+		repo = hgRepo;
+		toAdd = new LinkedHashSet<Path>();
+		toRemove = new LinkedHashSet<Path>();
+	}
+
+	/**
+	 * Schedule specified files to get listed in dirstate as added
+	 * 
+	 * @param paths files to mark as added, additive
+	 * @return <code>this</code> for convenience
+	 */
+	public HgAddRemoveCommand add(Path... paths) {
+		if (paths == null) {
+			throw new IllegalArgumentException();
+		}
+		for (Path p : paths) {
+			toRemove.remove(p);
+			toAdd.add(p);
+		}
+		return this;
+	}
+	
+	/**
+	 * Schedule specified files to be marked as removed
+	 * 
+	 * @param paths files to mark as removed, additive
+	 * @return <code>this</code> for convenience
+	 */
+	public HgAddRemoveCommand remove(Path... paths) {
+		if (paths == null) {
+			throw new IllegalArgumentException();
+		}
+		for (Path p : paths) {
+			toAdd.remove(p);
+			toRemove.add(p);
+		}
+		return this;
+	}
+	
+	public HgAddRemoveCommand addAll() {
+		throw Internals.notImplemented();
+	}
+	
+	public HgAddRemoveCommand forget(Path path) {
+		throw Internals.notImplemented();
+	}
+
+	/**
+	 * Perform scheduled addition/removal
+	 * 
+	 * @throws HgException
+	 */
+	public void execute() throws HgException {
+		try {
+			Internals implRepo = Internals.getInstance(repo);
+			final DirstateBuilder dirstateBuilder = new DirstateBuilder(implRepo);
+			dirstateBuilder.fillFrom(new DirstateReader(implRepo, new Path.SimpleSource()));
+			for (Path p : toAdd) {
+				dirstateBuilder.recordAdded(p, Flags.RegularFile, -1);
+			}
+			for (Path p : toRemove) {
+				dirstateBuilder.recordRemoved(p);
+			}
+			dirstateBuilder.serialize();
+		} catch (HgRuntimeException ex) {
+			throw new HgLibraryFailureException(ex);
+		}
+	}
+}
--- a/src/org/tmatesoft/hg/internal/DirstateBuilder.java	Thu Jan 17 19:23:52 2013 +0100
+++ b/src/org/tmatesoft/hg/internal/DirstateBuilder.java	Mon Jan 21 19:41:51 2013 +0100
@@ -83,11 +83,34 @@
 		normal.put(fname, new HgDirstate.Record(0, -1, -1, fname, null));
 	}
 	
-	private void forget(Path fname) {
-		normal.remove(fname);
-		added.remove(fname);
-		removed.remove(fname);
-		merged.remove(fname);
+	public void recordAdded(Path fname, Flags flags, int size) {
+		forget(fname);
+		added.put(fname, new HgDirstate.Record(0, -1, -1, fname, null));
+	}
+	
+	public void recordRemoved(Path fname) {
+		HgDirstate.Record r = forget(fname);
+		HgDirstate.Record n;
+		if (r == null) {
+			n = new HgDirstate.Record(0, -1, -1, fname, null);
+		} else {
+			n = new HgDirstate.Record(r.mode(), r.size(), r.modificationTime(), fname, r.copySource());
+		}
+		removed.put(fname, n);
+	}
+	
+	private HgDirstate.Record forget(Path fname) {
+		HgDirstate.Record r;
+		if ((r = normal.remove(fname)) != null) {
+			return r;
+		}
+		if ((r = added.remove(fname)) != null) {
+			return r;
+		}
+		if ((r = removed.remove(fname)) != null) {
+			return r;
+		}
+		return merged.remove(fname);
 	}
 
 	public void serialize(WritableByteChannel dest) throws IOException {
@@ -105,7 +128,10 @@
 		// entries
 		@SuppressWarnings("unchecked")
 		Map<Path, HgDirstate.Record>[] all = new Map[] {normal, added, removed, merged};
+		ByteBuffer recordTypes = ByteBuffer.allocate(4);
+		recordTypes.put((byte) 'n').put((byte) 'a').put((byte) 'r').put((byte) 'm').flip();
 		for (Map<Path, HgDirstate.Record> m : all) {
+			final byte recordType = recordTypes.get();
 			for (HgDirstate.Record r : m.values()) {
 				// regular entry is 1+4+4+4+4+fname.length bytes
 				// it might get extended with copy origin, prepended with 0 byte 
@@ -113,7 +139,7 @@
 				byte[] copyOrigin = r.copySource() == null ? null : encodingHelper.toDirstate(r.copySource());
 				int length = fname.length + (copyOrigin == null ? 0 : (1 + copyOrigin.length)); 
 				bb = ensureCapacity(bb, 17 + length);
-				bb.put((byte) 'n');
+				bb.put(recordType);
 				bb.putInt(r.mode());
 				bb.putInt(r.size());
 				bb.putInt(r.modificationTime());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/org/tmatesoft/hg/test/TestAddRemove.java	Mon Jan 21 19:41:51 2013 +0100
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2013 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.test;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.tmatesoft.hg.core.HgAddRemoveCommand;
+import org.tmatesoft.hg.repo.HgLookup;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestAddRemove {
+
+	@Rule
+	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();
+
+	private HgRepository repo;
+	private ExecHelper eh;
+
+	public TestAddRemove() {
+	}
+	
+	@Test
+	public void testScheduleAddition() throws Exception {
+		File testRepoLoc = TestRevert.cloneRepoToTempLocation("log-1", "test-addremove-1", false);
+		repo = new HgLookup().detect(testRepoLoc);
+		
+		StatusOutputParser statusParser = new StatusOutputParser();
+		eh = new ExecHelper(statusParser, testRepoLoc);
+		eh.run("hg", "status", "-A");
+		assertEquals("[sanity]", 0, statusParser.getUnknown().size());
+		assertEquals("[sanity]", 0, statusParser.getAdded().size());
+		//
+		createFile(new File(testRepoLoc, "one"), "1");
+		createFile(new File(testRepoLoc, "two"), "2");
+		statusParser.reset();
+		eh.run("hg", "status", "-A");
+		assertEquals("[sanity]", 2, statusParser.getUnknown().size());
+		assertEquals("[sanity]", 0, statusParser.getAdded().size());
+		
+		new HgAddRemoveCommand(repo).add(Path.create("one"), Path.create("two")).execute();
+		statusParser.reset();
+		eh.run("hg", "status", "-A");
+		assertEquals(0, statusParser.getUnknown().size());
+		assertEquals(2, statusParser.getAdded().size());
+	}
+	
+	@Test
+	public void testScheduleRemoval() throws Exception {
+		File testRepoLoc = TestRevert.cloneRepoToTempLocation("log-1", "test-addremove-2", false);
+		repo = new HgLookup().detect(testRepoLoc);
+
+		StatusOutputParser statusParser = new StatusOutputParser();
+		eh = new ExecHelper(statusParser, testRepoLoc);
+		eh.run("hg", "status", "-A");
+		assertEquals("[sanity]", 0, statusParser.getUnknown().size());
+		assertEquals("[sanity]", 0, statusParser.getRemoved().size());
+		
+		new HgAddRemoveCommand(repo).remove(Path.create("b"), Path.create("d")).execute();
+		statusParser.reset();
+		eh.run("hg", "status", "-A");
+		assertEquals(2, statusParser.getRemoved().size());
+	}
+
+	private static void createFile(File f, Object content) throws IOException {
+		FileOutputStream fos = new FileOutputStream(f, true);
+		fos.write(String.valueOf(content).getBytes());
+		fos.close();
+	}
+	
+}
--- a/test/org/tmatesoft/hg/test/TestRevert.java	Thu Jan 17 19:23:52 2013 +0100
+++ b/test/org/tmatesoft/hg/test/TestRevert.java	Mon Jan 21 19:41:51 2013 +0100
@@ -76,12 +76,16 @@
 		errorCollector.assertEquals(targetFile.toString() + ".orig", statusParser.getUnknown().get(0).toString());
 	}
 
-	private static void modifyFileAppend(File f) throws Exception {
+	private static void modifyFileAppend(File f) throws IOException {
 		assertTrue(f.isFile());
 		FileOutputStream fos = new FileOutputStream(f, true);
 		fos.write("XXX".getBytes());
 		fos.close();
 	}
+	
+	static File cloneRepoToTempLocation(String configRepoName, String name, boolean noupdate) throws Exception, InterruptedException {
+		return cloneRepoToTempLocation(Configuration.get().find(configRepoName), name, noupdate);
+	}
 
 	static File cloneRepoToTempLocation(HgRepository repo, String name, boolean noupdate) throws IOException, InterruptedException {
 		File testRepoLoc = TestIncoming.createEmptyDir(name);