# HG changeset patch # User Artem Tikhomirov # Date 1358793711 -3600 # Node ID 95bdcf75e71eed050cf559439b86d91e837b2e65 # Parent f7fbf48b938386d7df6c4a8303f6873a71ef4fdc Command to schedule addition/removal of repository files diff -r f7fbf48b9383 -r 95bdcf75e71e build.xml --- 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 @@ + diff -r f7fbf48b9383 -r 95bdcf75e71e src/org/tmatesoft/hg/core/HgAddRemoveCommand.java --- /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 { + + private final HgRepository repo; + private final LinkedHashSet toAdd, toRemove; + + public HgAddRemoveCommand(HgRepository hgRepo) { + repo = hgRepo; + toAdd = new LinkedHashSet(); + toRemove = new LinkedHashSet(); + } + + /** + * Schedule specified files to get listed in dirstate as added + * + * @param paths files to mark as added, additive + * @return this 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 this 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); + } + } +} diff -r f7fbf48b9383 -r 95bdcf75e71e src/org/tmatesoft/hg/internal/DirstateBuilder.java --- 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[] 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 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()); diff -r f7fbf48b9383 -r 95bdcf75e71e test/org/tmatesoft/hg/test/TestAddRemove.java --- /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(); + } + +} diff -r f7fbf48b9383 -r 95bdcf75e71e test/org/tmatesoft/hg/test/TestRevert.java --- 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);