# HG changeset patch # User Artem Tikhomirov # Date 1358266039 -3600 # Node ID 2f9ed6bcefa2e737c5f65832f5b299b4a1e5550f # Parent 0be5be8d57e9de88da31882e00abe184d950bb54 Initial support for Revert command with accompanying minor refactoring diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 build.xml --- a/build.xml Fri Jan 11 18:12:39 2013 +0100 +++ b/build.xml Tue Jan 15 17:07:19 2013 +0100 @@ -101,6 +101,8 @@ + + diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 cmdline/org/tmatesoft/hg/console/Main.java --- a/cmdline/org/tmatesoft/hg/console/Main.java Fri Jan 11 18:12:39 2013 +0100 +++ b/cmdline/org/tmatesoft/hg/console/Main.java Tue Jan 15 17:07:19 2013 +0100 @@ -36,6 +36,7 @@ import org.tmatesoft.hg.core.HgLogCommand; import org.tmatesoft.hg.core.HgManifestCommand; import org.tmatesoft.hg.core.HgManifestHandler; +import org.tmatesoft.hg.core.HgRevertCommand; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.BasicSessionContext; import org.tmatesoft.hg.internal.ByteArrayChannel; @@ -99,7 +100,8 @@ public static void main(String[] args) throws Exception { Main m = new Main(args); - m.testCheckout(); + m.testRevert(); +// m.testCheckout(); // m.tryExtensions(); // m.dumpBookmarks(); // m.readConfigFile(); @@ -125,6 +127,11 @@ // m.bunchOfTests(); } + private void testRevert() throws Exception { + HgRevertCommand cmd = new HgRevertCommand(hgRepo); + cmd.file(Path.create("a.txt")).execute(); + } + private void testCheckout() throws Exception { HgCheckoutCommand coCmd = new HgCheckoutCommand(hgRepo); coCmd.changeset(17).execute(); diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/core/HgCheckoutCommand.java --- a/src/org/tmatesoft/hg/core/HgCheckoutCommand.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/core/HgCheckoutCommand.java Tue Jan 15 17:07:19 2013 +0100 @@ -16,6 +16,8 @@ */ package org.tmatesoft.hg.core; +import static org.tmatesoft.hg.repo.HgRepositoryFiles.Dirstate; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -27,11 +29,10 @@ import org.tmatesoft.hg.internal.WorkingDirFileWriter; import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgInvalidRevisionException; -import org.tmatesoft.hg.repo.HgInvalidStateException; import org.tmatesoft.hg.repo.HgManifest; +import org.tmatesoft.hg.repo.HgManifest.Flags; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgRuntimeException; -import org.tmatesoft.hg.repo.HgManifest.Flags; import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.Path; @@ -93,61 +94,83 @@ * @throws CancelledException */ public void execute() throws HgException, CancelledException { - Internals internalRepo = Internals.getInstance(repo); - // remove tracked files from wd (perhaps, just forget 'Added'?) - // TODO - final DirstateBuilder dirstateBuilder = new DirstateBuilder(internalRepo.buildFileNameEncodingHelper()); - final Exception[] failure = new Exception[1]; - HgManifest.Inspector worker = new HgManifest.Inspector() { - - public boolean next(Nodeid nid, Path fname, Flags flags) { - try { - HgDataFile df = repo.getFileNode(fname); - int fileRevIndex = df.getRevisionIndex(nid); - // check out files based on manifest - // FIXME links! - WorkingDirFileWriter workingDirWriter = new WorkingDirFileWriter(repo); - workingDirWriter.processFile(df, fileRevIndex); - // new dirstate based on manifest - dirstateBuilder.recordNormal(fname, flags, workingDirWriter.bytesWritten()); + try { + Internals internalRepo = Internals.getInstance(repo); + // FIXME remove tracked files from wd (perhaps, just forget 'Added'?) + // TODO + final DirstateBuilder dirstateBuilder = new DirstateBuilder(internalRepo); + final CheckoutWorker worker = new CheckoutWorker(internalRepo); + HgManifest.Inspector insp = new HgManifest.Inspector() { + + public boolean next(Nodeid nid, Path fname, Flags flags) { + if (worker.next(nid, fname, flags)) { + // new dirstate based on manifest + dirstateBuilder.recordNormal(fname, flags, worker.getLastWrittenFileSize()); + return true; + } + return false; + } + + public boolean end(int manifestRevision) { + return false; + } + + public boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision) { return true; - } catch (IOException ex) { - failure[0] = ex; - } catch (HgRuntimeException ex) { - failure[0] = ex; } - return false; - } - - public boolean end(int manifestRevision) { - return false; + }; + dirstateBuilder.parents(repo.getChangelog().getRevision(revisionToCheckout), null); + repo.getManifest().walk(revisionToCheckout, revisionToCheckout, insp); + worker.checkFailed(); + File dirstateFile = internalRepo.getRepositoryFile(Dirstate); + try { + FileChannel dirstate = new FileOutputStream(dirstateFile).getChannel(); + dirstateBuilder.serialize(dirstate); + dirstate.close(); + } catch (IOException ex) { + throw new HgIOException("Can't write down new directory state", ex, dirstateFile); } - - public boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision) { + // FIXME write down branch file + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); + } + } + + static class CheckoutWorker { + private final Internals hgRepo; + private HgException failure; + private int lastWrittenFileSize; + + CheckoutWorker(Internals implRepo) { + hgRepo = implRepo; + } + + public boolean next(Nodeid nid, Path fname, Flags flags) { + try { + HgDataFile df = hgRepo.getRepo().getFileNode(fname); + int fileRevIndex = df.getRevisionIndex(nid); + // check out files based on manifest + // FIXME links! + WorkingDirFileWriter workingDirWriter = new WorkingDirFileWriter(hgRepo); + workingDirWriter.processFile(df, fileRevIndex); + lastWrittenFileSize = workingDirWriter.bytesWritten(); return true; - } - }; - dirstateBuilder.parents(repo.getChangelog().getRevision(revisionToCheckout), null); - repo.getManifest().walk(revisionToCheckout, revisionToCheckout, worker); - if (failure[0] != null) { - if (failure[0] instanceof IOException) { - throw new HgIOException("Failed to write down file revision", failure[0], /*FIXME file*/null); - } - if (failure[0] instanceof HgRuntimeException) { - throw new HgLibraryFailureException((HgRuntimeException) failure[0]); + } catch (IOException ex) { + failure = new HgIOException("Failed to write down file revision", ex, /*FIXME file*/null); + } catch (HgRuntimeException ex) { + failure = new HgLibraryFailureException(ex); } - HgInvalidStateException e = new HgInvalidStateException("Unexpected exception"); - e.initCause(failure[0]); - throw e; + return false; } - File dirstateFile = internalRepo.getFileFromRepoDir("dirstate"); - try { - FileChannel dirstate = new FileOutputStream(dirstateFile).getChannel(); - dirstateBuilder.serialize(dirstate); - dirstate.close(); - } catch (IOException ex) { - throw new HgIOException("Can't write down new directory state", ex, dirstateFile); + + public int getLastWrittenFileSize() { + return lastWrittenFileSize; } - // FIXME write down branch file - } + + public void checkFailed() throws HgException { + if (failure != null) { + throw failure; + } + } + }; } diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/core/HgIncomingCommand.java --- a/src/org/tmatesoft/hg/core/HgIncomingCommand.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/core/HgIncomingCommand.java Tue Jan 15 17:07:19 2013 +0100 @@ -25,6 +25,7 @@ import java.util.Set; import java.util.TreeSet; +import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.internal.RepositoryComparator; import org.tmatesoft.hg.internal.RepositoryComparator.BranchChain; import org.tmatesoft.hg.repo.HgBundle; @@ -95,7 +96,7 @@ */ public HgIncomingCommand subrepo(boolean include) { includeSubrepo = include; - throw HgRepository.notImplemented(); + throw Internals.notImplemented(); } /** diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/core/HgLogCommand.java --- a/src/org/tmatesoft/hg/core/HgLogCommand.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/core/HgLogCommand.java Tue Jan 15 17:07:19 2013 +0100 @@ -38,6 +38,7 @@ import org.tmatesoft.hg.internal.BatchRangeHelper; import org.tmatesoft.hg.internal.IntMap; import org.tmatesoft.hg.internal.IntVector; +import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.internal.Lifecycle; import org.tmatesoft.hg.internal.LifecycleProxy; import org.tmatesoft.hg.repo.HgChangelog; @@ -143,7 +144,7 @@ this.date = date; // TODO post-1.0 implement // isSet(field) - false => don't use in detection of 'same date' - throw HgRepository.notImplemented(); + throw Internals.notImplemented(); } /** diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/core/HgOutgoingCommand.java --- a/src/org/tmatesoft/hg/core/HgOutgoingCommand.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/core/HgOutgoingCommand.java Tue Jan 15 17:07:19 2013 +0100 @@ -20,6 +20,7 @@ import java.util.Set; import java.util.TreeSet; +import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.internal.RepositoryComparator; import org.tmatesoft.hg.repo.HgChangelog; import org.tmatesoft.hg.repo.HgInvalidControlFileException; @@ -88,7 +89,7 @@ */ public HgOutgoingCommand subrepo(boolean include) { includeSubrepo = include; - throw HgRepository.notImplemented(); + throw Internals.notImplemented(); } /** diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/core/HgRevertCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgRevertCommand.java Tue Jan 15 17:07:19 2013 +0100 @@ -0,0 +1,160 @@ +/* + * 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.io.File; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +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.HgInvalidRevisionException; +import org.tmatesoft.hg.repo.HgManifest; +import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgRuntimeException; +import org.tmatesoft.hg.repo.HgManifest.Flags; +import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.Path; + +/** + * WORK IN PROGRESS. + * + * Restore files to their checkout state, 'hg revert' counterpart. + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +@Experimental(reason="Work in progress") +public class HgRevertCommand extends HgAbstractCommand { + + private final HgRepository repo; + private final Set files = new LinkedHashSet(); + private int changesetToCheckout = HgRepository.WORKING_COPY; // XXX WORKING_COPY_PARENT, in fact + private boolean keepOriginal = true; + + public HgRevertCommand(HgRepository hgRepo) { + repo = hgRepo; + } + + /** + * Additive + * + * @param paths files to revert + * @return this for convenience + */ + public HgRevertCommand file(Path... paths) { + files.addAll(Arrays.asList(paths)); + return this; + } + + /** + * Revert the given files to their states as of a specific revision + * + * @param changesetRevIndex + * @return this for convenience + * @throws HgBadArgumentException + */ + public HgRevertCommand changeset(int changesetRevIndex) throws HgBadArgumentException { + int lastCsetIndex = repo.getChangelog().getLastRevision(); + if (changesetRevIndex < 0 || changesetRevIndex > lastCsetIndex) { + throw new HgBadArgumentException(String.format("Bad revision index %d, value from [0..%d] expected", changesetRevIndex, lastCsetIndex), null).setRevisionIndex(changesetRevIndex); + } + changesetToCheckout = changesetRevIndex; + return this; + } + + /** + * Handy supplement to {@link #changeset(int)} + * + * @param revision + * @return this for convenience + * @throws HgBadArgumentException + */ + public HgRevertCommand changeset(Nodeid revision) throws HgBadArgumentException { + try { + return changeset(repo.getChangelog().getRevisionIndex(revision)); + } catch (HgInvalidRevisionException ex) { + throw new HgBadArgumentException("Can't find revision", ex).setRevision(revision); + } + } + + // TODO keepOriginal() to save .orig + + /** + * Perform the back out for the given files + * + * @throws HgIOException + * @throws HgException + * @throws CancelledException + */ + public void execute() throws HgException, CancelledException { + try { + final int csetRevision; + if (changesetToCheckout == HgRepository.WORKING_COPY) { + csetRevision = repo.getChangelog().getRevisionIndex(repo.getWorkingCopyParents().first()); + } else { + csetRevision = changesetToCheckout; + } + Internals implRepo = Internals.getInstance(repo); + final DirstateBuilder dirstateBuilder = new DirstateBuilder(implRepo); + dirstateBuilder.fillFrom(new DirstateReader(implRepo, new Path.SimpleSource())); + final HgCheckoutCommand.CheckoutWorker worker = new HgCheckoutCommand.CheckoutWorker(implRepo); + + HgManifest.Inspector insp = new HgManifest.Inspector() { + + public boolean next(Nodeid nid, Path fname, Flags flags) { + if (worker.next(nid, fname, flags)) { + dirstateBuilder.recordUncertain(fname); + return true; + } + return false; + } + + public boolean end(int manifestRevision) { + return false; + } + + public boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision) { + return true; + } + }; + + for (Path file : files) { + File f = new File(repo.getWorkingDir(), file.toString()); + if (f.isFile()) { + if (keepOriginal) { + File copy = new File(f.getParentFile(), f.getName() + ".orig"); + if (copy.exists()) { + copy.delete(); + } + f.renameTo(copy); + } else { + f.delete(); + } + } + repo.getManifest().walkFileRevisions(file, insp, csetRevision); + worker.checkFailed(); + } + dirstateBuilder.serialize(); + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); + } + } +} diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/core/HgStatusCommand.java --- a/src/org/tmatesoft/hg/core/HgStatusCommand.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/core/HgStatusCommand.java Tue Jan 15 17:07:19 2013 +0100 @@ -24,6 +24,7 @@ import java.util.ConcurrentModificationException; import org.tmatesoft.hg.internal.ChangelogHelper; +import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.repo.HgStatusCollector; @@ -156,7 +157,7 @@ } public HgStatusCommand subrepo(boolean visit) { - throw HgRepository.notImplemented(); + throw Internals.notImplemented(); } /** diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/internal/DirstateBuilder.java --- a/src/org/tmatesoft/hg/internal/DirstateBuilder.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/internal/DirstateBuilder.java Tue Jan 15 17:07:19 2013 +0100 @@ -16,15 +16,23 @@ */ package org.tmatesoft.hg.internal; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; -import java.util.ArrayList; -import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import org.tmatesoft.hg.core.HgIOException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.repo.HgDirstate; +import org.tmatesoft.hg.repo.HgDirstate.EntryKind; +import org.tmatesoft.hg.repo.HgDirstate.Record; +import org.tmatesoft.hg.repo.HgInvalidStateException; import org.tmatesoft.hg.repo.HgManifest.Flags; +import org.tmatesoft.hg.repo.HgRepositoryFiles; import org.tmatesoft.hg.util.Path; /** @@ -36,12 +44,17 @@ * @author TMate Software Ltd. */ public class DirstateBuilder { - private List normal = new ArrayList(); + private Map normal = new TreeMap(); + private Map added = new TreeMap(); + private Map removed = new TreeMap(); + private Map merged = new TreeMap(); private Nodeid parent1, parent2; + private final Internals hgRepo; private final EncodingHelper encodingHelper; - public DirstateBuilder(EncodingHelper encHelper) { - encodingHelper = encHelper; + public DirstateBuilder(Internals internalRepo) { + hgRepo = internalRepo; + encodingHelper = internalRepo.buildFileNameEncodingHelper(); } public void parents(Nodeid p1, Nodeid p2) { @@ -60,8 +73,21 @@ // right away. int fmode = flags == Flags.RegularFile ? 0666 : 0777; // FIXME actual unix flags int mtime = (int) (System.currentTimeMillis() / 1000); - normal.add(new HgDirstate.Record(fmode, bytesWritten, mtime,fname, null)); - + forget(fname); + normal.put(fname, new HgDirstate.Record(fmode, bytesWritten, mtime, fname, null)); + } + + public void recordUncertain(Path fname) { + // `hg revert` puts "n 0 -1 unset" for the reverted file, so shall we + forget(fname); + 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 serialize(WritableByteChannel dest) throws IOException { @@ -77,24 +103,65 @@ } bb.clear(); // entries - for (HgDirstate.Record r : normal) { - // normal entry is 1+4+4+4+4+fname.length bytes - byte[] fname = encodingHelper.toDirstate(r.name().toString()); - bb = ensureCapacity(bb, 17 + fname.length); - bb.put((byte) 'n'); - bb.putInt(r.mode()); - bb.putInt(r.size()); - bb.putInt(r.modificationTime()); - bb.putInt(fname.length); - bb.put(fname); - bb.flip(); - written = dest.write(bb); - if (written != bb.limit()) { - throw new IOException("Incomplete write"); + @SuppressWarnings("unchecked") + Map[] all = new Map[] {normal, added, removed, merged}; + for (Map m : all) { + 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 + byte[] fname = encodingHelper.toDirstate(r.name()); + 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.putInt(r.mode()); + bb.putInt(r.size()); + bb.putInt(r.modificationTime()); + bb.putInt(length); + bb.put(fname); + if (copyOrigin != null) { + bb.put((byte) 0); + bb.put(copyOrigin); + } + bb.flip(); + written = dest.write(bb); + if (written != bb.limit()) { + throw new IOException("Incomplete write"); + } + bb.clear(); } - bb.clear(); } } + + public void serialize() throws HgIOException { + File dirstateFile = hgRepo.getRepositoryFile(HgRepositoryFiles.Dirstate); + try { + FileChannel dirstate = new FileOutputStream(dirstateFile).getChannel(); + serialize(dirstate); + dirstate.close(); + } catch (IOException ex) { + throw new HgIOException("Can't write down new directory state", ex, dirstateFile); + } + } + + public void fillFrom(DirstateReader dirstate) { + // TODO preserve order, if reasonable and possible + dirstate.readInto(new HgDirstate.Inspector() { + + public boolean next(EntryKind kind, Record entry) { + switch (kind) { + case Normal: normal.put(entry.name(), entry); break; + case Added : added.put(entry.name(), entry); break; + case Removed : removed.put(entry.name(), entry); break; + case Merged : merged.put(entry.name(), entry); break; + default: throw new HgInvalidStateException(String.format("Unexpected entry in the dirstate: %s", kind)); + } + return true; + } + }); + parents(dirstate.parents().first(), dirstate.parents().second()); + } + private static ByteBuffer ensureCapacity(ByteBuffer buf, int cap) { if (buf.capacity() >= cap) { diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/internal/DirstateReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/DirstateReader.java Tue Jan 15 17:07:19 2013 +0100 @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2010-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.internal; + +import static org.tmatesoft.hg.core.Nodeid.NULL; +import static org.tmatesoft.hg.repo.HgRepositoryFiles.Dirstate; +import static org.tmatesoft.hg.util.LogFacility.Severity.Debug; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.repo.HgDirstate; +import org.tmatesoft.hg.repo.HgDirstate.EntryKind; +import org.tmatesoft.hg.repo.HgInvalidControlFileException; +import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.util.LogFacility.Severity; +import org.tmatesoft.hg.util.Pair; +import org.tmatesoft.hg.util.Path; + + +/** + * Parse dirstate file + * + * @see http://mercurial.selenic.com/wiki/DirState + * @see http://mercurial.selenic.com/wiki/FileFormats#dirstate + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public final class DirstateReader { + // dirstate read code originally lived in org.tmatesoft.hg.repo.HgDirstate + + private final Internals repo; + private final Path.Source pathPool; + private Pair parents; + + public DirstateReader(Internals hgRepo, Path.Source pathSource) { + repo = hgRepo; + pathPool = pathSource; + } + + public void readInto(HgDirstate.Inspector target) throws HgInvalidControlFileException { + EncodingHelper encodingHelper = repo.buildFileNameEncodingHelper(); + parents = new Pair(Nodeid.NULL, Nodeid.NULL); + File dirstateFile = getDirstateFile(repo); + if (dirstateFile == null || !dirstateFile.exists()) { + return; + } + DataAccess da = repo.getDataAccess().create(dirstateFile); + try { + if (da.isEmpty()) { + return; + } + parents = internalReadParents(da); + // hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only + while (!da.isEmpty()) { + final byte state = da.readByte(); + final int fmode = da.readInt(); + final int size = da.readInt(); + final int time = da.readInt(); + final int nameLen = da.readInt(); + String fn1 = null, fn2 = null; + byte[] name = new byte[nameLen]; + da.readBytes(name, 0, nameLen); + for (int i = 0; i < nameLen; i++) { + if (name[i] == 0) { + fn1 = encodingHelper.fromDirstate(name, 0, i); + fn2 = encodingHelper.fromDirstate(name, i+1, nameLen - i - 1); + break; + } + } + if (fn1 == null) { + fn1 = encodingHelper.fromDirstate(name, 0, nameLen); + } + HgDirstate.Record r = new HgDirstate.Record(fmode, size, time, pathPool.path(fn1), fn2 == null ? null : pathPool.path(fn2)); + if (state == 'n') { + target.next(EntryKind.Normal, r); + } else if (state == 'a') { + target.next(EntryKind.Added, r); + } else if (state == 'r') { + target.next(EntryKind.Removed, r); + } else if (state == 'm') { + target.next(EntryKind.Merged, r); + } else { + repo.getSessionContext().getLog().dump(getClass(), Severity.Warn, "Dirstate record for file %s (size: %d, tstamp:%d) has unknown state '%c'", r.name(), r.size(), r.modificationTime(), state); + } + } + } catch (IOException ex) { + throw new HgInvalidControlFileException("Dirstate read failed", ex, dirstateFile); + } finally { + da.done(); + } + } + + private static Pair internalReadParents(DataAccess da) throws IOException { + byte[] parents = new byte[40]; + da.readBytes(parents, 0, 40); + Nodeid n1 = Nodeid.fromBinary(parents, 0); + Nodeid n2 = Nodeid.fromBinary(parents, 20); + parents = null; + return new Pair(n1, n2); + } + + /** + * @return pair of working copy parents, with {@link Nodeid#NULL} for missing values. + */ + public Pair parents() { + assert parents != null; // instance not initialized with #read() + return parents; + } + + private static File getDirstateFile(Internals repo) { + return repo.getFileFromRepoDir(Dirstate.getName()); + } + + /** + * @return pair of parents, both {@link Nodeid#NULL} if dirstate is not available + */ + public static Pair readParents(Internals internalRepo) throws HgInvalidControlFileException { + // do not read whole dirstate if all we need is WC parent information + File dirstateFile = getDirstateFile(internalRepo); + if (dirstateFile == null || !dirstateFile.exists()) { + return new Pair(NULL, NULL); + } + DataAccess da = internalRepo.getDataAccess().create(dirstateFile); + try { + if (da.isEmpty()) { + return new Pair(NULL, NULL); + } + return internalReadParents(da); + } catch (IOException ex) { + throw new HgInvalidControlFileException("Error reading working copy parents from dirstate", ex, dirstateFile); + } finally { + da.done(); + } + } + + /** + * TODO [post-1.0] it's really not a proper place for the method, need WorkingCopyContainer or similar + * @return branch associated with the working directory + */ + public static String readBranch(Internals internalRepo) throws HgInvalidControlFileException { + File branchFile = internalRepo.getFileFromRepoDir("branch"); // FIXME constant in the HgRepositoryFiles + String branch = HgRepository.DEFAULT_BRANCH_NAME; + if (branchFile.exists()) { + try { + BufferedReader r = new BufferedReader(new FileReader(branchFile)); + String b = r.readLine(); + if (b != null) { + b = b.trim().intern(); + } + branch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b; + r.close(); + } catch (FileNotFoundException ex) { + internalRepo.getSessionContext().getLog().dump(HgDirstate.class, Debug, ex, null); // log verbose debug, exception might be legal here + // IGNORE + } catch (IOException ex) { + throw new HgInvalidControlFileException("Error reading file with branch information", ex, branchFile); + } + } + return branch; + } +} diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/internal/EncodingHelper.java --- a/src/org/tmatesoft/hg/internal/EncodingHelper.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/internal/EncodingHelper.java Tue Jan 15 17:07:19 2013 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2012 TMate Software Ltd + * Copyright (c) 2011-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 @@ -60,7 +60,7 @@ /** * @return byte representation of the string directly comparable to bytes in manifest */ - public byte[] toManifest(String s) { + public byte[] toManifest(CharSequence s) { if (s == null) { // perhaps, can return byte[0] in this case? throw new IllegalArgumentException(); @@ -75,7 +75,7 @@ return decodeWithSystemDefaultFallback(data, start, length); } - public byte[] toDirstate(String fname) { + public byte[] toDirstate(CharSequence fname) { if (fname == null) { throw new IllegalArgumentException(); } @@ -92,7 +92,7 @@ } } - private byte[] encodeWithSystemDefaultFallback(String s) { + private byte[] encodeWithSystemDefaultFallback(CharSequence s) { try { // synchronized(encoder) { ByteBuffer bb = encoder.encode(CharBuffer.wrap(s)); @@ -103,7 +103,7 @@ } catch (CharacterCodingException ex) { sessionContext.getLog().dump(getClass(), Error, ex, String.format("Use of charset %s failed, resort to system default", charset().name())); // resort to system-default - return s.getBytes(); + return s.toString().getBytes(); } } diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/internal/Internals.java --- a/src/org/tmatesoft/hg/internal/Internals.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/internal/Internals.java Tue Jan 15 17:07:19 2013 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2012 TMate Software Ltd + * Copyright (c) 2011-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 @@ -32,9 +32,11 @@ import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgInternals; -import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.repo.HgRepoConfig.ExtensionsSection; import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgRepositoryFiles; +import org.tmatesoft.hg.repo.HgRepositoryLock; +import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.util.PathRewrite; /** @@ -117,6 +119,10 @@ return !repoDir.exists() || !repoDir.isDirectory(); } + public File getRepositoryFile(HgRepositoryFiles f) { + return f.residesUnderRepositoryRoot() ? getFileFromRepoDir(f.getName()) : getFileFromDataDir(f.getName()); + } + /** * Access files under ".hg/". * File not necessarily exists, this method is merely a factory for Files at specific, configuration-dependent location. @@ -222,7 +228,7 @@ return dataPathHelper.rewrite(df.getPath().toString()); } - + public static boolean runningOnWindows() { return System.getProperty("os.name").indexOf("Windows") != -1; } @@ -411,6 +417,11 @@ return shallCacheRevlogsInRepo; } + // marker method + public static IllegalStateException notImplemented() { + return new IllegalStateException("Not implemented"); + } + public static Internals getInstance(HgRepository repo) { return HgInternals.getImplementationRepo(repo); } diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/internal/WorkingDirFileWriter.java --- a/src/org/tmatesoft/hg/internal/WorkingDirFileWriter.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/internal/WorkingDirFileWriter.java Tue Jan 15 17:07:19 2013 +0100 @@ -23,11 +23,10 @@ import java.nio.channels.FileChannel; import org.tmatesoft.hg.repo.HgDataFile; -import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.ByteChannel; import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.LogFacility.Severity; import org.tmatesoft.hg.util.Path; -import org.tmatesoft.hg.util.LogFacility.Severity; /** * @@ -37,13 +36,13 @@ public class WorkingDirFileWriter implements ByteChannel { - private final HgRepository repo; + private final Internals hgRepo; private File dest; private FileChannel destChannel; private int totalBytesWritten; - public WorkingDirFileWriter(HgRepository hgRepo) { - repo = hgRepo; + public WorkingDirFileWriter(Internals internalRepo) { + hgRepo = internalRepo; } public void processFile(HgDataFile df, int fileRevIndex) throws IOException { @@ -51,14 +50,14 @@ prepare(df.getPath()); df.contentWithFilters(fileRevIndex, this); } catch (CancelledException ex) { - repo.getSessionContext().getLog().dump(getClass(), Severity.Error, ex, "Our impl doesn't throw cancellation"); + hgRepo.getSessionContext().getLog().dump(getClass(), Severity.Error, ex, "Our impl doesn't throw cancellation"); } finish(); } public void prepare(Path fname) throws IOException { String fpath = fname.toString(); - dest = new File(repo.getWorkingDir(), fpath); + dest = new File(hgRepo.getRepo().getWorkingDir(), fpath); if (fpath.indexOf('/') != -1) { dest.getParentFile().mkdirs(); } diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/repo/HgBundle.java --- a/src/org/tmatesoft/hg/repo/HgBundle.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/repo/HgBundle.java Tue Jan 15 17:07:19 2013 +0100 @@ -30,6 +30,7 @@ import org.tmatesoft.hg.internal.DigestHelper; import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.InflaterDataAccess; +import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.internal.Lifecycle; import org.tmatesoft.hg.internal.Patch; import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; @@ -68,7 +69,7 @@ return new InflaterDataAccess(da, 6, da.length() - 6); } if (signature[4] == 'B' && signature[5] == 'Z') { - throw HgRepository.notImplemented(); + throw Internals.notImplemented(); } if (signature[4] != 'U' || signature[5] != 'N') { throw new HgInvalidStateException(String.format("Bad bundle signature: %s", String.valueOf(signature))); diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/repo/HgDirstate.java --- a/src/org/tmatesoft/hg/repo/HgDirstate.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/repo/HgDirstate.java Tue Jan 15 17:07:19 2013 +0100 @@ -16,15 +16,6 @@ */ package org.tmatesoft.hg.repo; -import static org.tmatesoft.hg.core.Nodeid.NULL; -import static org.tmatesoft.hg.repo.HgRepositoryFiles.Dirstate; -import static org.tmatesoft.hg.util.LogFacility.Severity.Debug; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; @@ -32,13 +23,11 @@ import java.util.TreeSet; import org.tmatesoft.hg.core.Nodeid; -import org.tmatesoft.hg.internal.DataAccess; -import org.tmatesoft.hg.internal.EncodingHelper; +import org.tmatesoft.hg.internal.DirstateReader; import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.util.Pair; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.PathRewrite; -import org.tmatesoft.hg.util.LogFacility.Severity; /** @@ -75,7 +64,6 @@ } /*package-local*/ void read() throws HgInvalidControlFileException { - EncodingHelper encodingHelper = repo.buildFileNameEncodingHelper(); normal = added = removed = merged = Collections.emptyMap(); parents = new Pair(Nodeid.NULL, Nodeid.NULL); if (canonicalPathRewrite != null) { @@ -83,87 +71,45 @@ } else { canonical2dirstateName = Collections.emptyMap(); } - File dirstateFile = getDirstateFile(repo); - if (dirstateFile == null || !dirstateFile.exists()) { - return; - } - DataAccess da = repo.getDataAccess().create(dirstateFile); - try { - if (da.isEmpty()) { - return; - } - // not sure linked is really needed here, just for ease of debug - normal = new LinkedHashMap(); - added = new LinkedHashMap(); - removed = new LinkedHashMap(); - merged = new LinkedHashMap(); + // not sure linked is really needed here, just for ease of debug + normal = new LinkedHashMap(); + added = new LinkedHashMap(); + removed = new LinkedHashMap(); + merged = new LinkedHashMap(); + + DirstateReader dirstateReader = new DirstateReader(repo, pathPool); + dirstateReader.readInto(new Inspector() { - parents = internalReadParents(da); - // hg init; hg up produces an empty repository where dirstate has parents (40 bytes) only - while (!da.isEmpty()) { - final byte state = da.readByte(); - final int fmode = da.readInt(); - final int size = da.readInt(); - final int time = da.readInt(); - final int nameLen = da.readInt(); - String fn1 = null, fn2 = null; - byte[] name = new byte[nameLen]; - da.readBytes(name, 0, nameLen); - for (int i = 0; i < nameLen; i++) { - if (name[i] == 0) { - fn1 = encodingHelper.fromDirstate(name, 0, i); - fn2 = encodingHelper.fromDirstate(name, i+1, nameLen - i - 1); - break; - } - } - if (fn1 == null) { - fn1 = encodingHelper.fromDirstate(name, 0, nameLen); - } - Record r = new Record(fmode, size, time, pathPool.path(fn1), fn2 == null ? null : pathPool.path(fn2)); + public boolean next(EntryKind kind, Record r) { if (canonicalPathRewrite != null) { - Path canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(fn1).toString()); + Path canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(r.name())); if (canonicalPath != r.name()) { // == as they come from the same pool assert !canonical2dirstateName.containsKey(canonicalPath); // otherwise there's already a file with same canonical name // which can't happen for case-insensitive file system (or there's erroneous PathRewrite, perhaps doing smth else) canonical2dirstateName.put(canonicalPath, r.name()); } - if (fn2 != null) { + if (r.copySource() != null) { // not sure I need copy origin in the map, I don't seem to use it anywhere, // but I guess I'll have to use it some day. - canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(fn2).toString()); + canonicalPath = pathPool.path(canonicalPathRewrite.rewrite(r.copySource())); if (canonicalPath != r.copySource()) { canonical2dirstateName.put(canonicalPath, r.copySource()); } } } - if (state == 'n') { - normal.put(r.name1, r); - } else if (state == 'a') { - added.put(r.name1, r); - } else if (state == 'r') { - removed.put(r.name1, r); - } else if (state == 'm') { - merged.put(r.name1, r); - } else { - repo.getSessionContext().getLog().dump(getClass(), Severity.Warn, "Dirstate record for file %s (size: %d, tstamp:%d) has unknown state '%c'", r.name1, r.size(), r.time, state); + switch (kind) { + case Normal : normal.put(r.name(), r); break; + case Added : added.put(r.name(), r); break; + case Removed : removed.put(r.name(), r); break; + case Merged : merged.put(r.name1, r); break; + default: throw new HgInvalidStateException(String.format("Unexpected entry in the dirstate: %s", kind)); } + return true; } - } catch (IOException ex) { - throw new HgInvalidControlFileException("Dirstate read failed", ex, dirstateFile); - } finally { - da.done(); - } + }); + parents = dirstateReader.parents(); } - private static Pair internalReadParents(DataAccess da) throws IOException { - byte[] parents = new byte[40]; - da.readBytes(parents, 0, 40); - Nodeid n1 = Nodeid.fromBinary(parents, 0); - Nodeid n2 = Nodeid.fromBinary(parents, 20); - parents = null; - return new Pair(n1, n2); - } - /** * @return pair of working copy parents, with {@link Nodeid#NULL} for missing values. */ @@ -172,58 +118,6 @@ return parents; } - private static File getDirstateFile(Internals repo) { - return repo.getFileFromRepoDir(Dirstate.getName()); - } - - /** - * @return pair of parents, both {@link Nodeid#NULL} if dirstate is not available - */ - /*package-local*/ static Pair readParents(Internals internalRepo) throws HgInvalidControlFileException { - // do not read whole dirstate if all we need is WC parent information - File dirstateFile = getDirstateFile(internalRepo); - if (dirstateFile == null || !dirstateFile.exists()) { - return new Pair(NULL, NULL); - } - DataAccess da = internalRepo.getDataAccess().create(dirstateFile); - try { - if (da.isEmpty()) { - return new Pair(NULL, NULL); - } - return internalReadParents(da); - } catch (IOException ex) { - throw new HgInvalidControlFileException("Error reading working copy parents from dirstate", ex, dirstateFile); - } finally { - da.done(); - } - } - - /** - * TODO [post-1.0] it's really not a proper place for the method, need WorkingCopyContainer or similar - * @return branch associated with the working directory - */ - /*package-local*/ static String readBranch(Internals internalRepo) throws HgInvalidControlFileException { - File branchFile = internalRepo.getFileFromRepoDir("branch"); - String branch = HgRepository.DEFAULT_BRANCH_NAME; - if (branchFile.exists()) { - try { - BufferedReader r = new BufferedReader(new FileReader(branchFile)); - String b = r.readLine(); - if (b != null) { - b = b.trim().intern(); - } - branch = b == null || b.length() == 0 ? HgRepository.DEFAULT_BRANCH_NAME : b; - r.close(); - } catch (FileNotFoundException ex) { - internalRepo.getSessionContext().getLog().dump(HgDirstate.class, Debug, ex, null); // log verbose debug, exception might be legal here - // IGNORE - } catch (IOException ex) { - throw new HgInvalidControlFileException("Error reading file with branch information", ex, branchFile); - } - } - return branch; - } - // new, modifiable collection /*package-local*/ TreeSet all() { assert normal != null; diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/repo/HgLookup.java --- a/src/org/tmatesoft/hg/repo/HgLookup.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/repo/HgLookup.java Tue Jan 15 17:07:19 2013 +0100 @@ -28,6 +28,7 @@ import org.tmatesoft.hg.internal.BasicSessionContext; import org.tmatesoft.hg.internal.ConfigFile; import org.tmatesoft.hg.internal.DataAccessProvider; +import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.repo.HgRepoConfig.PathsSection; /** @@ -129,7 +130,7 @@ throw new IllegalArgumentException(); } if (Boolean.FALSE.booleanValue()) { - throw HgRepository.notImplemented(); + throw Internals.notImplemented(); } return new HgRemoteRepository(getContext(), url); } diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/repo/HgRepository.java --- a/src/org/tmatesoft/hg/repo/HgRepository.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/repo/HgRepository.java Tue Jan 15 17:07:19 2013 +0100 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2012 TMate Software Ltd + * Copyright (c) 2010-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 @@ -34,6 +34,7 @@ import org.tmatesoft.hg.core.SessionContext; import org.tmatesoft.hg.internal.ByteArrayChannel; import org.tmatesoft.hg.internal.ConfigFile; +import org.tmatesoft.hg.internal.DirstateReader; import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.Filter; import org.tmatesoft.hg.internal.Internals; @@ -93,11 +94,6 @@ */ public static final String DEFAULT_BRANCH_NAME = "default"; - // temp aux marker method - public static IllegalStateException notImplemented() { - return new IllegalStateException("Not implemented"); - } - private final File repoDir; // .hg folder private final File workingDir; // .hg/../ private final String repoLocation; @@ -299,7 +295,7 @@ * @throws HgInvalidControlFileException if attempt to read information about working copy parents from dirstate failed */ public Pair getWorkingCopyParents() throws HgInvalidControlFileException { - return HgDirstate.readParents(impl); + return DirstateReader.readParents(impl); } /** @@ -308,7 +304,7 @@ */ public String getWorkingCopyBranchName() throws HgInvalidControlFileException { if (wcBranch == null) { - wcBranch = HgDirstate.readBranch(impl); + wcBranch = DirstateReader.readBranch(impl); } return wcBranch; } @@ -350,8 +346,8 @@ return repoConfig; } - // XXX package-local, unless there are cases when required from outside (guess, working dir/revision walkers may hide dirstate access and no public visibility needed) - // XXX consider passing Path pool or factory to produce (shared) Path instead of Strings + // There seem to be no cases when access to HgDirstate is required from outside + // (guess, working dir/revision walkers may hide dirstate access and no public visibility needed) /*package-local*/ final HgDirstate loadDirstate(Path.Source pathFactory) throws HgInvalidControlFileException { PathRewrite canonicalPath = null; if (!impl.isCaseSensitiveFileSystem()) { diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 src/org/tmatesoft/hg/repo/HgSubrepoLocation.java --- a/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java Fri Jan 11 18:12:39 2013 +0100 +++ b/src/org/tmatesoft/hg/repo/HgSubrepoLocation.java Tue Jan 15 17:07:19 2013 +0100 @@ -20,6 +20,7 @@ import org.tmatesoft.hg.core.HgRepositoryNotFoundException; import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.internal.Internals; import org.tmatesoft.hg.util.Path; /** @@ -109,7 +110,7 @@ * @return true if it's dirty */ public boolean hasChanges() { - throw HgRepository.notImplemented(); + throw Internals.notImplemented(); } /** diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 test/org/tmatesoft/hg/test/LogOutputParser.java --- a/test/org/tmatesoft/hg/test/LogOutputParser.java Fri Jan 11 18:12:39 2013 +0100 +++ b/test/org/tmatesoft/hg/test/LogOutputParser.java Tue Jan 15 17:07:19 2013 +0100 @@ -21,7 +21,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.internal.Internals; /** @@ -46,7 +46,7 @@ pattern5 = Pattern.compile("\\n\\n"); //p = "^manifest:\\s+(\\d+):([a-f0-9]{40})\nuser:(.+)$"; } else { - throw HgRepository.notImplemented(); + throw Internals.notImplemented(); } } diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 test/org/tmatesoft/hg/test/TestCheckout.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/org/tmatesoft/hg/test/TestCheckout.java Tue Jan 15 17:07:19 2013 +0100 @@ -0,0 +1,44 @@ +/* + * 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 org.junit.Assert; +import org.junit.Test; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class TestCheckout { + + + @Test + public void testCleanCheckoutInEmptyDir() { + Assert.fail("clone without update, checkout, status"); + } + + @Test + public void testCleanCheckoutInDirtyDir() { + Assert.fail("Make sure WC is cleared prior to clean checkout"); + } + + @Test + public void testBranchCheckout() { + Assert.fail("Make sure branch file is written"); + } +} diff -r 0be5be8d57e9 -r 2f9ed6bcefa2 test/org/tmatesoft/hg/test/TestRevert.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/org/tmatesoft/hg/test/TestRevert.java Tue Jan 15 17:07:19 2013 +0100 @@ -0,0 +1,87 @@ +/* + * 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 static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileOutputStream; + +import org.junit.Rule; +import org.junit.Test; +import org.tmatesoft.hg.core.HgRevertCommand; +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 TestRevert { + + @Rule + public ErrorCollectorExt errorCollector = new ErrorCollectorExt(); + private HgRepository repo; + private ExecHelper eh; + + + public TestRevert() { + } + + @Test + public void testCommand() throws Exception { + File tmpDir = Configuration.get().getTempDir(); + tmpDir.mkdirs(); + eh = new ExecHelper(new OutputParser.Stub(), tmpDir); + File testRepoLoc = TestIncoming.createEmptyDir("test-revert"); + // get a copy of a repository + eh.run("hg", "clone", Configuration.get().find("log-1").getWorkingDir().toString(), testRepoLoc.getName()); + assertEquals("[sanity]", 0, eh.getExitValue()); + + repo = new HgLookup().detect(testRepoLoc); + Path targetFile = Path.create("b"); + modifyFileAppend(new File(testRepoLoc, targetFile.toString())); + + StatusOutputParser statusParser = new StatusOutputParser(); + eh = new ExecHelper(statusParser, testRepoLoc); + eh.run("hg", "status", "-A"); + assertEquals("[sanity]", 1, statusParser.getModified().size()); + assertEquals("[sanity]", 2, statusParser.getClean().size()); + assertEquals("[sanity]", targetFile, statusParser.getModified().get(0)); + + HgRevertCommand cmd = new HgRevertCommand(repo); + cmd.file(targetFile).execute(); + statusParser.reset(); + eh.run("hg", "status", "-A"); + + assertEquals(3, statusParser.getClean().size()); + assertTrue(statusParser.getClean().contains(targetFile)); + assertEquals(1, statusParser.getUnknown().size()); + assertEquals(targetFile.toString() + ".orig", statusParser.getUnknown().get(0).toString()); + } + + private static void modifyFileAppend(File f) throws Exception { + assertTrue(f.isFile()); + FileOutputStream fos = new FileOutputStream(f, true); + fos.write("XXX".getBytes()); + fos.close(); + } +}