# HG changeset patch # User Artem Tikhomirov # Date 1373399205 -7200 # Node ID 4fd317a2fecfdf1e8ac7e35856528815c0c25ac4 # Parent d10399f80f4ed3aa9838f0a9ebffb277eb3fc49b Pull: phase1 get remote changes and add local revisions diff -r d10399f80f4e -r 4fd317a2fecf src/org/tmatesoft/hg/core/HgPullCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgPullCommand.java Tue Jul 09 21:46:45 2013 +0200 @@ -0,0 +1,111 @@ +/* + * 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.List; + +import org.tmatesoft.hg.internal.AddRevInspector; +import org.tmatesoft.hg.internal.COWTransaction; +import org.tmatesoft.hg.internal.Internals; +import org.tmatesoft.hg.internal.PhasesHelper; +import org.tmatesoft.hg.internal.RepositoryComparator; +import org.tmatesoft.hg.internal.RevisionSet; +import org.tmatesoft.hg.internal.Transaction; +import org.tmatesoft.hg.repo.HgBundle; +import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgInternals; +import org.tmatesoft.hg.repo.HgParentChildMap; +import org.tmatesoft.hg.repo.HgRemoteRepository; +import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgRuntimeException; +import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.ProgressSupport; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class HgPullCommand extends HgAbstractCommand { + + private final HgRepository repo; + private HgRemoteRepository remote; + + public HgPullCommand(HgRepository hgRepo) { + repo = hgRepo; + } + + public HgPullCommand source(HgRemoteRepository hgRemote) { + remote = hgRemote; + return this; + } + + public void execute() throws HgRemoteConnectionException, HgIOException, HgLibraryFailureException, CancelledException { + final ProgressSupport progress = getProgressSupport(null); + try { + progress.start(100); + // TODO refactor same code in HgIncomingCommand #getComparator and #getParentHelper + final HgChangelog clog = repo.getChangelog(); + final HgParentChildMap parentHelper = new HgParentChildMap(clog); + parentHelper.init(); + final RepositoryComparator comparator = new RepositoryComparator(parentHelper, remote); + // get incoming revisions + comparator.compare(new ProgressSupport.Sub(progress, 50), getCancelSupport(null, true)); + final List common = comparator.getCommon(); + // get bundle with changes from remote + HgBundle incoming = remote.getChanges(common); + // + // add revisions to changelog, manifest, files + final Internals implRepo = HgInternals.getImplementationRepo(repo); + final AddRevInspector insp; + Transaction.Factory trFactory = new COWTransaction.Factory(); + Transaction tr = trFactory.create(repo); + try { + incoming.inspectAll(insp = new AddRevInspector(implRepo, tr)); + tr.commit(); + } catch (HgRuntimeException ex) { + tr.rollback(); + throw ex; + } catch (RuntimeException ex) { + tr.rollback(); + throw ex; + } + progress.worked(45); + RevisionSet added = insp.addedChangesets(); + + // get remote phases, update local phases to match that of remote + final PhasesHelper phaseHelper = new PhasesHelper(implRepo, parentHelper); + if (phaseHelper.isCapableOfPhases()) { + RevisionSet rsCommon = new RevisionSet(common); + HgRemoteRepository.Phases remotePhases = remote.getPhases(); + if (remotePhases.isPublishingServer()) { + final RevisionSet knownPublic = rsCommon.union(added); + RevisionSet newDraft = phaseHelper.allDraft().subtract(knownPublic); + RevisionSet newSecret = phaseHelper.allSecret().subtract(knownPublic); + phaseHelper.updateRoots(newDraft.asList(), newSecret.asList()); + } else { + // FIXME refactor reuse from HgPushCommand + } + } + progress.worked(5); + } catch (HgRuntimeException ex) { + throw new HgLibraryFailureException(ex); + } finally { + progress.done(); + } + } +} diff -r d10399f80f4e -r 4fd317a2fecf src/org/tmatesoft/hg/core/HgRepoFacade.java --- a/src/org/tmatesoft/hg/core/HgRepoFacade.java Thu Jul 04 21:09:33 2013 +0200 +++ b/src/org/tmatesoft/hg/core/HgRepoFacade.java Tue Jul 09 21:46:45 2013 +0200 @@ -169,4 +169,8 @@ public HgPushCommand createPushCommand() { return new HgPushCommand(repo); } + + public HgPullCommand createPullCommand() { + return new HgPullCommand(repo); + } } diff -r d10399f80f4e -r 4fd317a2fecf src/org/tmatesoft/hg/internal/AddRevInspector.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/AddRevInspector.java Tue Jul 09 21:46:45 2013 +0200 @@ -0,0 +1,128 @@ +/* + * 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.internal; + +import java.util.HashMap; +import java.util.Set; + +import org.tmatesoft.hg.core.HgIOException; +import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.repo.HgBundle; +import org.tmatesoft.hg.repo.HgBundle.GroupElement; +import org.tmatesoft.hg.repo.HgDataFile; +import org.tmatesoft.hg.repo.HgInvalidControlFileException; +import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgRuntimeException; +import org.tmatesoft.hg.util.Pair; + +/** + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public final class AddRevInspector implements HgBundle.Inspector { + private final Internals repo; + private final Transaction tr; + private Set added; + private RevlogStreamWriter revlog; + private RevMap clogRevs; + private RevMap revlogRevs; + + public AddRevInspector(Internals implRepo, Transaction transaction) { + repo = implRepo; + tr = transaction; + } + + public void changelogStart() throws HgRuntimeException { + // TODO Auto-generated method stub + RevlogStream rs = repo.getImplAccess().getChangelogStream(); + revlog = new RevlogStreamWriter(repo, rs, tr); + revlogRevs = clogRevs = new RevMap(rs); + } + + public void changelogEnd() throws HgRuntimeException { + revlog = null; + revlogRevs = null; + added = clogRevs.added(); + } + + public void manifestStart() throws HgRuntimeException { + RevlogStream rs = repo.getImplAccess().getManifestStream(); + revlog = new RevlogStreamWriter(repo, rs, tr); + revlogRevs = new RevMap(rs); + } + + public void manifestEnd() throws HgRuntimeException { + revlog = null; + revlogRevs = null; + } + + public void fileStart(String name) throws HgRuntimeException { + HgDataFile df = repo.getRepo().getFileNode(name); + RevlogStream rs = repo.getImplAccess().getStream(df); + revlog = new RevlogStreamWriter(repo, rs, tr); + revlogRevs = new RevMap(rs); + // FIXME collect new files and update fncache + } + + public void fileEnd(String name) throws HgRuntimeException { + revlog = null; + revlogRevs = null; + } + + public boolean element(GroupElement ge) throws HgRuntimeException { + assert clogRevs != null; + assert revlogRevs != null; + try { + Pair newRev = revlog.addPatchRevision(ge, clogRevs, revlogRevs); + revlogRevs.update(newRev.first(), newRev.second()); + return true; + } catch (HgIOException ex) { + throw new HgInvalidControlFileException(ex, true); + } + } + + public RevisionSet addedChangesets() { + return new RevisionSet(added); + } + + private static class RevMap implements RevlogStreamWriter.RevisionToIndexMap { + + private final RevlogStream revlog; + private HashMap added = new HashMap(); + + public RevMap(RevlogStream revlogStream) { + revlog = revlogStream; + } + + public int revisionIndex(Nodeid revision) { + Integer a = added.get(revision); + if (a != null) { + return a; + } + int f = revlog.findRevisionIndex(revision); + return f == HgRepository.BAD_REVISION ? HgRepository.NO_REVISION : f; + } + + public void update(Integer revIndex, Nodeid rev) { + added.put(rev, revIndex); + } + + Set added() { + return added.keySet(); + } + } +} \ No newline at end of file diff -r d10399f80f4e -r 4fd317a2fecf src/org/tmatesoft/hg/internal/CommitFacility.java --- a/src/org/tmatesoft/hg/internal/CommitFacility.java Thu Jul 04 21:09:33 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/CommitFacility.java Tue Jul 09 21:46:45 2013 +0200 @@ -157,7 +157,7 @@ newlyAddedFiles.put(df.getPath(), contentStream); } RevlogStreamWriter fileWriter = new RevlogStreamWriter(repo, contentStream, transaction); - Nodeid fileRev = fileWriter.addRevision(bds, clogRevisionIndex, fp.first(), fp.second()); + Nodeid fileRev = fileWriter.addRevision(bds, clogRevisionIndex, fp.first(), fp.second()).second(); newManifestRevision.put(df.getPath(), fileRev); touchInDirstate.add(df.getPath()); } @@ -168,7 +168,7 @@ manifestBuilder.add(me.getKey().toString(), me.getValue()); } RevlogStreamWriter manifestWriter = new RevlogStreamWriter(repo, repo.getImplAccess().getManifestStream(), transaction); - Nodeid manifestRev = manifestWriter.addRevision(manifestBuilder, clogRevisionIndex, manifestParents.first(), manifestParents.second()); + Nodeid manifestRev = manifestWriter.addRevision(manifestBuilder, clogRevisionIndex, manifestParents.first(), manifestParents.second()).second(); // // Changelog final ChangelogEntryBuilder changelogBuilder = new ChangelogEntryBuilder(); @@ -177,7 +177,7 @@ changelogBuilder.user(String.valueOf(user)); changelogBuilder.manifest(manifestRev).comment(message); RevlogStreamWriter changelogWriter = new RevlogStreamWriter(repo, repo.getImplAccess().getChangelogStream(), transaction); - Nodeid changesetRev = changelogWriter.addRevision(changelogBuilder, clogRevisionIndex, p1Commit, p2Commit); + Nodeid changesetRev = changelogWriter.addRevision(changelogBuilder, clogRevisionIndex, p1Commit, p2Commit).second(); // TODO move fncache update to an external facility, along with dirstate and bookmark update if (!newlyAddedFiles.isEmpty() && repo.fncacheInUse()) { FNCacheFile fncache = new FNCacheFile(repo); diff -r d10399f80f4e -r 4fd317a2fecf src/org/tmatesoft/hg/internal/RevlogStreamWriter.java --- a/src/org/tmatesoft/hg/internal/RevlogStreamWriter.java Thu Jul 04 21:09:33 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/RevlogStreamWriter.java Tue Jul 09 21:46:45 2013 +0200 @@ -25,13 +25,16 @@ import org.tmatesoft.hg.core.HgIOException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.core.SessionContext; +import org.tmatesoft.hg.internal.DataSerializer.ByteArrayDataSource; import org.tmatesoft.hg.internal.DataSerializer.ByteArraySerializer; -import org.tmatesoft.hg.internal.DataSerializer.ByteArrayDataSource; import org.tmatesoft.hg.internal.DataSerializer.DataSource; +import org.tmatesoft.hg.repo.HgBundle.GroupElement; import org.tmatesoft.hg.repo.HgInvalidControlFileException; import org.tmatesoft.hg.repo.HgInvalidRevisionException; import org.tmatesoft.hg.repo.HgInvalidStateException; +import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgRuntimeException; +import org.tmatesoft.hg.util.Pair; /** * @@ -45,8 +48,10 @@ private final DigestHelper dh = new DigestHelper(); private final RevlogCompressor revlogDataZip; private final Transaction transaction; - private int lastEntryBase, lastEntryIndex; - private byte[] lastEntryContent; + private int lastEntryBase, lastEntryIndex, lastEntryActualLen; + // record revision and its full content + // the name might be misleading, it does not necessarily match lastEntryIndex + private Pair lastFullContent; private Nodeid lastEntryRevision; private IntMap revisionCache = new IntMap(32); private RevlogStream revlogStream; @@ -61,22 +66,98 @@ transaction = tr; } + public Pair addPatchRevision(GroupElement ge, RevisionToIndexMap clogRevs, RevisionToIndexMap revlogRevs) throws HgIOException, HgRuntimeException { + populateLastEntryIndex(); + // + final Nodeid nodeRev = ge.node(); + final Nodeid csetRev = ge.cset(); + int linkRev; + if (nodeRev.equals(csetRev)) { + linkRev = lastEntryIndex+1; + } else { + linkRev = clogRevs.revisionIndex(csetRev); + } + assert linkRev >= 0; + final Nodeid p1Rev = ge.firstParent(); + int p1 = p1Rev.isNull() ? NO_REVISION : revlogRevs.revisionIndex(p1Rev); + final Nodeid p2Rev = ge.secondParent(); + int p2 = p2Rev.isNull() ? NO_REVISION : revlogRevs.revisionIndex(p2Rev); + Patch p = new Patch(); + final byte[] patchBytes; + try { + // XXX there's ge.rawData(), to avoid extra array wrap + patchBytes = ge.rawDataByteArray(); + p.read(new ByteArrayDataAccess(patchBytes)); + } catch (IOException ex) { + throw new HgIOException("Failed to read patch information", ex, null); + } + // + final Nodeid patchBase = ge.patchBase(); + int patchBaseRev = patchBase.isNull() ? NO_REVISION : revlogRevs.revisionIndex(patchBase); + int baseRev = lastEntryIndex == NO_REVISION ? 0 : revlogStream.baseRevision(patchBaseRev); + int revLen; + DataSource ds; + byte[] complete = null; + if (patchBaseRev == lastEntryIndex && lastEntryIndex != NO_REVISION) { + // we may write patch from GroupElement as is + int patchBaseLen = dataLength(patchBaseRev); + revLen = patchBaseLen + p.patchSizeDelta(); + ds = new ByteArrayDataSource(patchBytes); + } else { + // read baseRev, unless it's the pull to empty repository + try { + if (lastEntryIndex == NO_REVISION) { + complete = p.apply(new ByteArrayDataAccess(new byte[0]), -1); + baseRev = 0; // it's done above, but doesn't hurt + } else { + ReadContentInspector insp = new ReadContentInspector().read(revlogStream, baseRev); + complete = p.apply(new ByteArrayDataAccess(insp.content), -1); + baseRev = lastEntryIndex + 1; + } + ds = new ByteArrayDataSource(complete); + revLen = complete.length; + } catch (IOException ex) { + // unlikely to happen, as ByteArrayDataSource doesn't throw IOException + throw new HgIOException("Failed to reconstruct revision", ex, null); + } + } + doAdd(nodeRev, p1, p2, linkRev, baseRev, revLen, ds); + if (complete != null) { + lastFullContent = new Pair(lastEntryIndex, complete); + } + return new Pair(lastEntryIndex, lastEntryRevision); + } + /** * @return nodeid of added revision * @throws HgRuntimeException */ - public Nodeid addRevision(DataSource content, int linkRevision, int p1, int p2) throws HgIOException, HgRuntimeException { - lastEntryRevision = Nodeid.NULL; - int revCount = revlogStream.revisionCount(); - lastEntryIndex = revCount == 0 ? NO_REVISION : revCount - 1; - populateLastEntry(); + public Pair addRevision(DataSource content, int linkRevision, int p1, int p2) throws HgIOException, HgRuntimeException { + populateLastEntryIndex(); + populateLastEntryContent(); // byte[] contentByteArray = toByteArray(content); - Patch patch = GeneratePatchInspector.delta(lastEntryContent, contentByteArray); + Patch patch = GeneratePatchInspector.delta(lastFullContent.second(), contentByteArray); int patchSerializedLength = patch.serializedLength(); final boolean writeComplete = preferCompleteOverPatch(patchSerializedLength, contentByteArray.length); DataSerializer.DataSource dataSource = writeComplete ? new ByteArrayDataSource(contentByteArray) : patch.new PatchDataSource(); + // + Nodeid p1Rev = revision(p1); + Nodeid p2Rev = revision(p2); + Nodeid newRev = Nodeid.fromBinary(dh.sha1(p1Rev, p2Rev, contentByteArray).asBinary(), 0); + doAdd(newRev, p1, p2, linkRevision, writeComplete ? lastEntryIndex+1 : lastEntryBase, contentByteArray.length, dataSource); + lastFullContent = new Pair(lastEntryIndex, contentByteArray); + return new Pair(lastEntryIndex, lastEntryRevision); + } + + private Nodeid doAdd(Nodeid rev, int p1, int p2, int linkRevision, int baseRevision, int revLen, DataSerializer.DataSource dataSource) throws HgIOException, HgRuntimeException { + assert linkRevision >= 0; + assert baseRevision >= 0; + assert p1 == NO_REVISION || p1 >= 0; + assert p2 == NO_REVISION || p2 >= 0; + assert !rev.isNull(); + assert revLen >= 0; revlogDataZip.reset(dataSource); final int compressedLen; final boolean useCompressedData = preferCompressedOverComplete(revlogDataZip.getCompressedLength(), dataSource.serializeLength()); @@ -87,11 +168,6 @@ compressedLen = dataSource.serializeLength() + 1 /*1 byte for 'u' - uncompressed prefix byte*/; } // - Nodeid p1Rev = revision(p1); - Nodeid p2Rev = revision(p2); - byte[] revisionNodeidBytes = dh.sha1(p1Rev, p2Rev, contentByteArray).asBinary(); - // - DataSerializer indexFile, dataFile; indexFile = dataFile = null; try { @@ -99,11 +175,11 @@ indexFile = revlogStream.getIndexStreamWriter(transaction); final boolean isInlineData = revlogStream.isInlineData(); HeaderWriter revlogHeader = new HeaderWriter(isInlineData); - revlogHeader.length(contentByteArray.length, compressedLen); - revlogHeader.nodeid(revisionNodeidBytes); + revlogHeader.length(revLen, compressedLen); + revlogHeader.nodeid(rev.toByteArray()); revlogHeader.linkRevision(linkRevision); revlogHeader.parents(p1, p2); - revlogHeader.baseRevision(writeComplete ? lastEntryIndex+1 : lastEntryBase); + revlogHeader.baseRevision(baseRevision); long lastEntryOffset = revlogStream.newEntryOffset(); revlogHeader.offset(lastEntryOffset); // @@ -124,11 +200,10 @@ dataSource.serialize(dataFile); } - - lastEntryContent = contentByteArray; lastEntryBase = revlogHeader.baseRevision(); lastEntryIndex++; - lastEntryRevision = Nodeid.fromBinary(revisionNodeidBytes, 0); + lastEntryActualLen = revLen; + lastEntryRevision = rev; revisionCache.put(lastEntryIndex, lastEntryRevision); revlogStream.revisionAdded(lastEntryIndex, lastEntryRevision, lastEntryBase, lastEntryOffset); @@ -159,32 +234,38 @@ return n; } - private void populateLastEntry() throws HgRuntimeException { - if (lastEntryContent != null) { + private int dataLength(int revisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException { + assert revisionIndex >= 0; + if (revisionIndex == lastEntryIndex) { + return lastEntryActualLen; + } + if (lastFullContent != null && lastFullContent.first() == revisionIndex) { + return lastFullContent.second().length; + } + return revlogStream.dataLength(revisionIndex); + } + + private void populateLastEntryIndex() throws HgRuntimeException { + int revCount = revlogStream.revisionCount(); + lastEntryIndex = revCount == 0 ? NO_REVISION : revCount - 1; + } + + private void populateLastEntryContent() throws HgRuntimeException { + if (lastFullContent != null && lastFullContent.first() == lastEntryIndex) { + // we have last entry cached return; } + lastEntryRevision = Nodeid.NULL; if (lastEntryIndex != NO_REVISION) { - assert lastEntryIndex >= 0; - final IOException[] failure = new IOException[1]; - revlogStream.iterate(lastEntryIndex, lastEntryIndex, true, new RevlogStream.Inspector() { - - public void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { - try { - lastEntryBase = baseRevision; - lastEntryRevision = Nodeid.fromBinary(nodeid, 0); - lastEntryContent = data.byteArray(); - } catch (IOException ex) { - failure[0] = ex; - } - } - }); - if (failure[0] != null) { - String m = String.format("Failed to get content of most recent revision %d", lastEntryIndex); - throw revlogStream.initWithDataFile(new HgInvalidControlFileException(m, failure[0], null)); - } + ReadContentInspector insp = new ReadContentInspector().read(revlogStream, lastEntryIndex); + lastEntryBase = insp.baseRev; + lastEntryRevision = insp.rev; + lastFullContent = new Pair(lastEntryIndex, insp.content); } else { - lastEntryContent = new byte[0]; + lastFullContent = new Pair(lastEntryIndex, new byte[0]); } + assert lastFullContent.first() == lastEntryIndex; + assert lastFullContent.second() != null; } public static boolean preferCompleteOverPatch(int patchLength, int fullContentLength) { @@ -290,4 +371,40 @@ return header.capacity(); } } -} + + // XXX part of HgRevisionMap contract, need public counterparts (along with IndexToRevisionMap) + public interface RevisionToIndexMap { + + /** + * @return {@link HgRepository#NO_REVISION} if unknown revision + */ + int revisionIndex(Nodeid revision); + } + + private static class ReadContentInspector implements RevlogStream.Inspector { + public int baseRev; + public Nodeid rev; + public byte[] content; + private IOException failure; + + public ReadContentInspector read(RevlogStream rs, int revIndex) throws HgInvalidControlFileException { + assert revIndex >= 0; + rs.iterate(revIndex, revIndex, true, this); + if (failure != null) { + String m = String.format("Failed to get content of revision %d", revIndex); + throw rs.initWithDataFile(new HgInvalidControlFileException(m, failure, null)); + } + return this; + } + + public void next(int revisionIndex, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { + try { + baseRev = baseRevision; + rev = Nodeid.fromBinary(nodeid, 0); + content = data.byteArray(); + } catch (IOException ex) { + failure = ex; + } + } + } +} \ No newline at end of file diff -r d10399f80f4e -r 4fd317a2fecf test/org/tmatesoft/hg/test/RepoUtils.java --- a/test/org/tmatesoft/hg/test/RepoUtils.java Thu Jul 04 21:09:33 2013 +0200 +++ b/test/org/tmatesoft/hg/test/RepoUtils.java Tue Jul 09 21:46:45 2013 +0200 @@ -199,4 +199,10 @@ } return allRevs; } + + static void assertHgVerifyOk(ErrorCollectorExt errorCollector, File repoLoc) throws InterruptedException, IOException { + ExecHelper verifyRun = new ExecHelper(new OutputParser.Stub(), repoLoc); + verifyRun.run("hg", "verify"); + errorCollector.assertEquals("hg verify", 0, verifyRun.getExitValue()); + } } diff -r d10399f80f4e -r 4fd317a2fecf test/org/tmatesoft/hg/test/TestCommit.java --- a/test/org/tmatesoft/hg/test/TestCommit.java Thu Jul 04 21:09:33 2013 +0200 +++ b/test/org/tmatesoft/hg/test/TestCommit.java Tue Jul 09 21:46:45 2013 +0200 @@ -20,7 +20,6 @@ import static org.tmatesoft.hg.repo.HgRepository.*; import java.io.File; -import java.io.IOException; import java.util.List; import org.junit.Rule; @@ -160,7 +159,7 @@ // check if cached value in hgRepo got updated errorCollector.assertEquals("branch1", hgRepo.getWorkingCopyBranchName()); // - assertHgVerifyOk(repoLoc); + RepoUtils.assertHgVerifyOk(errorCollector, repoLoc); } /** @@ -192,7 +191,7 @@ new HgCatCommand(hgRepo).file(Path.create("xx")).changeset(commitRev).execute(sink); assertArrayEquals("xyz".getBytes(), sink.toArray()); // - assertHgVerifyOk(repoLoc); + RepoUtils.assertHgVerifyOk(errorCollector, repoLoc); } /** * perform few commits one by one, into different branches @@ -240,7 +239,7 @@ errorCollector.assertEquals("FIRST", c1.getComment()); errorCollector.assertEquals("SECOND", c2.getComment()); errorCollector.assertEquals("THIRD", c3.getComment()); - assertHgVerifyOk(repoLoc); + RepoUtils.assertHgVerifyOk(errorCollector, repoLoc); } @Test @@ -289,7 +288,7 @@ errorCollector.assertEquals(csets.get(1).getNodeid(), c2); errorCollector.assertEquals(csets.get(0).getComment(), "FIRST"); errorCollector.assertEquals(csets.get(1).getComment(), "SECOND"); - assertHgVerifyOk(repoLoc); + RepoUtils.assertHgVerifyOk(errorCollector, repoLoc); // new commits are drafts by default, check our commit respects this // TODO more tests with children of changesets with draft, secret or public phases (latter - // new commit is child of public, but there are other commits with draft/secret phases - ensure they are intact) @@ -488,13 +487,7 @@ errorCollector.assertTrue(status.get(Kind.Modified).contains(dfB.getPath())); errorCollector.assertTrue(status.get(Kind.Removed).contains(dfD.getPath())); - assertHgVerifyOk(repoLoc); - } - - private void assertHgVerifyOk(File repoLoc) throws InterruptedException, IOException { - ExecHelper verifyRun = new ExecHelper(new OutputParser.Stub(), repoLoc); - verifyRun.run("hg", "verify"); - errorCollector.assertEquals("hg verify", 0, verifyRun.getExitValue()); + RepoUtils.assertHgVerifyOk(errorCollector, repoLoc); } private Transaction newTransaction(SessionContext.Source ctxSource) { diff -r d10399f80f4e -r 4fd317a2fecf test/org/tmatesoft/hg/test/TestPull.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/org/tmatesoft/hg/test/TestPull.java Tue Jul 09 21:46:45 2013 +0200 @@ -0,0 +1,72 @@ +/* + * 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.tmatesoft.hg.repo.HgRepository.TIP; + +import java.io.File; +import java.util.List; + +import org.junit.Rule; +import org.junit.Test; +import org.tmatesoft.hg.core.HgIncomingCommand; +import org.tmatesoft.hg.core.HgPullCommand; +import org.tmatesoft.hg.core.Nodeid; +import org.tmatesoft.hg.repo.HgLookup; +import org.tmatesoft.hg.repo.HgRemoteRepository; +import org.tmatesoft.hg.repo.HgRepository; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class TestPull { + + @Rule + public ErrorCollectorExt errorCollector = new ErrorCollectorExt(); + + @Test + public void testPullToEmpty() throws Exception { + File srcRepoLoc = RepoUtils.cloneRepoToTempLocation("test-annotate", "test-pull2empty-src", false); + File dstRepoLoc = RepoUtils.initEmptyTempRepo("test-pull2empty-dst"); + HgServer server = new HgServer().start(srcRepoLoc); + try { + final HgLookup hgLookup = new HgLookup(); + final HgRemoteRepository srcRemote = hgLookup.detect(server.getURL()); + HgRepository dstRepo = hgLookup.detect(dstRepoLoc); + HgPullCommand cmd = new HgPullCommand(dstRepo).source(srcRemote); + cmd.execute(); + final HgRepository srcRepo = hgLookup.detect(srcRepoLoc); + checkRepositoriesAreSame(srcRepo, dstRepo); + final List incoming = new HgIncomingCommand(dstRepo).against(srcRemote).executeLite(); + errorCollector.assertTrue(incoming.toString(), incoming.isEmpty()); + RepoUtils.assertHgVerifyOk(errorCollector, dstRepoLoc); + } finally { + server.stop(); + } + } + + // test when pull comes with new file (if AddRevInspector/RevlogStreamWriter is ok with file that doesn't exist + + private void checkRepositoriesAreSame(HgRepository srcRepo, HgRepository dstRepo) { + // XXX copy of TestPush#checkRepositoriesAreSame + errorCollector.assertEquals(srcRepo.getChangelog().getRevisionCount(), dstRepo.getChangelog().getRevisionCount()); + errorCollector.assertEquals(srcRepo.getChangelog().getRevision(0), dstRepo.getChangelog().getRevision(0)); + errorCollector.assertEquals(srcRepo.getChangelog().getRevision(TIP), dstRepo.getChangelog().getRevision(TIP)); + } +}