view test/org/tmatesoft/hg/test/TestPush.java @ 677:1c49c0cee540

Report line number at the first appearance, like 'hg annotate -l' does
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 18 Jul 2013 18:47:45 +0200
parents 46b56864b483
children
line wrap: on
line source
/*
 * 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.*;
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.HgCheckoutCommand;
import org.tmatesoft.hg.core.HgCommitCommand;
import org.tmatesoft.hg.core.HgOutgoingCommand;
import org.tmatesoft.hg.core.HgPushCommand;
import org.tmatesoft.hg.core.Nodeid;
import org.tmatesoft.hg.internal.PhasesHelper;
import org.tmatesoft.hg.internal.RevisionSet;
import org.tmatesoft.hg.repo.HgBookmarks;
import org.tmatesoft.hg.repo.HgChangelog;
import org.tmatesoft.hg.repo.HgInternals;
import org.tmatesoft.hg.repo.HgLookup;
import org.tmatesoft.hg.repo.HgPhase;
import org.tmatesoft.hg.repo.HgRemoteRepository;
import org.tmatesoft.hg.repo.HgRepository;

/**
 * @author Artem Tikhomirov
 * @author TMate Software Ltd.
 */
public class TestPush {

	@Rule
	public ErrorCollectorExt errorCollector = new ErrorCollectorExt();

	@Test
	public void testPushToEmpty() throws Exception {
		File srcRepoLoc = RepoUtils.cloneRepoToTempLocation("test-annotate", "test-push2empty-src", false);
		File dstRepoLoc = RepoUtils.initEmptyTempRepo("test-push2empty-dst");
		HgServer server = new HgServer().start(dstRepoLoc);
		try {
			final HgLookup hgLookup = new HgLookup();
			HgRepository srcRepo = hgLookup.detect(srcRepoLoc);
			HgPushCommand cmd = new HgPushCommand(srcRepo);
			final HgRemoteRepository dstRemote = hgLookup.detect(server.getURL());
			cmd.destination(dstRemote);
			cmd.execute();
			final HgRepository dstRepo = hgLookup.detect(dstRepoLoc);
			checkRepositoriesAreSame(srcRepo, dstRepo);
			final List<Nodeid> outgoing = new HgOutgoingCommand(srcRepo).against(dstRemote).executeLite();
			errorCollector.assertTrue(outgoing.toString(), outgoing.isEmpty());
		} finally {
			server.stop();
		}
	}

	@Test
	public void testPushChanges() throws Exception {
		File srcRepoLoc = RepoUtils.cloneRepoToTempLocation("test-annotate", "test-push-src", false);
		File dstRepoLoc = RepoUtils.cloneRepoToTempLocation("test-annotate", "test-push-dst", false);
		File f1 = new File(srcRepoLoc, "file1");
		assertTrue("[sanity]", f1.canWrite());
		HgServer server = new HgServer().start(dstRepoLoc);
		try {
			final HgLookup hgLookup = new HgLookup();
			final HgRepository srcRepo = hgLookup.detect(srcRepoLoc);
			final HgRemoteRepository dstRemote = hgLookup.detect(server.getURL());
			RepoUtils.modifyFileAppend(f1, "change1");
			new HgCommitCommand(srcRepo).message("Commit 1").execute();
			new HgCheckoutCommand(srcRepo).changeset(7).clean(true).execute();
			assertEquals("[sanity]", "no-merge", srcRepo.getWorkingCopyBranchName());
			RepoUtils.modifyFileAppend(f1, "change2");
			new HgCommitCommand(srcRepo).message("Commit 2").execute();
			//
			new HgPushCommand(srcRepo).destination(dstRemote).execute();
			checkRepositoriesAreSame(srcRepo, hgLookup.detect(dstRepoLoc));
			final List<Nodeid> outgoing = new HgOutgoingCommand(srcRepo).against(dstRemote).executeLite();
			errorCollector.assertTrue(outgoing.toString(), outgoing.isEmpty());
		} finally {
			server.stop();
		}
	}
	
	@Test
	public void testPushToNonPublishingServer() throws Exception {
		// check drafts are same as on server
		// copy, not clone as latter updates phase information
		File srcRepoLoc = RepoUtils.copyRepoToTempLocation("test-phases", "test-push-nopub-src");
		File dstRepoLoc = RepoUtils.initEmptyTempRepo("test-push-nopub-dst");
		File f1 = new File(srcRepoLoc, "hello.c");
		assertTrue("[sanity]", f1.canWrite());
		HgServer server = new HgServer().publishing(false).start(dstRepoLoc);
		try {
			final HgLookup hgLookup = new HgLookup();
			final HgRepository srcRepo = hgLookup.detect(srcRepoLoc);
			final HgRemoteRepository dstRemote = hgLookup.detect(server.getURL());
			PhasesHelper phaseHelper = new PhasesHelper(HgInternals.getImplementationRepo(srcRepo));
			final RevisionSet allDraft = phaseHelper.allDraft();
			assertFalse("[sanity]", allDraft.isEmpty());
			final int publicCsetToBranchAt = 4;
			assertEquals("[sanity]", HgPhase.Public, phaseHelper.getPhase(publicCsetToBranchAt, null));
			// in addition to existing draft csets, add one more draft, branching at some other public revision
			new HgCheckoutCommand(srcRepo).changeset(publicCsetToBranchAt).clean(true).execute();
			RepoUtils.modifyFileAppend(f1, "// aaa");
			final HgCommitCommand commitCmd = new HgCommitCommand(srcRepo).message("Commit aaa");
			assertTrue(commitCmd.execute().isOk());
			Nodeid newCommit = commitCmd.getCommittedRevision();
			//
			new HgPushCommand(srcRepo).destination(dstRemote).execute();
			HgRepository dstRepo = hgLookup.detect(dstRepoLoc);
			final HgChangelog srcClog = srcRepo.getChangelog();
			final HgChangelog dstClog = dstRepo.getChangelog();
			// refresh PhasesHelper
			phaseHelper = new PhasesHelper(HgInternals.getImplementationRepo(srcRepo));
			// check if phase didn't change
			errorCollector.assertEquals(HgPhase.Draft, phaseHelper.getPhase(srcClog.getRevisionIndex(newCommit), newCommit));
			for (Nodeid n : allDraft) {
				// check drafts from src were actually pushed to dst 
				errorCollector.assertTrue(dstClog.isKnown(n));
				// check drafts didn't change their phase
				errorCollector.assertEquals(HgPhase.Draft, phaseHelper.getPhase(srcClog.getRevisionIndex(n), n));
			}
		} finally {
			server.stop();
		}
	}
	
	/**
	 * If server lists revisions we know as drafts as public, update them locally
	 */
	@Test
	public void testPushUpdatesPublishedDrafts() throws Exception {
		/* o		r9, secret
		 * |  o		r8, draft
		 * |  |
		 * |  o		r7, draft
		 * o  |		r6, secret 
		 * | /
		 * o		r5, draft
		 * |
		 * o		r4, public
		 */
		// remote: r5 -> public, r6 -> draft, r8 -> secret
		// local: new draft from r4, push
		File srcRepoLoc = RepoUtils.copyRepoToTempLocation("test-phases", "test-push-phase-update-1-src");
		File dstRepoLoc = RepoUtils.copyRepoToTempLocation("test-phases", "test-push-phase-update-1-dst");
		File f1 = new File(srcRepoLoc, "hello.c");
		assertTrue("[sanity]", f1.canWrite());
		final HgLookup hgLookup = new HgLookup();
		final HgRepository srcRepo = hgLookup.detect(srcRepoLoc);
		final ExecHelper dstRun = new ExecHelper(new OutputParser.Stub(), dstRepoLoc);
		final int publicCsetToBranchAt = 4;
		final int r5 = 5, r6 = 6, r8 = 8;
		PhasesHelper srcPhase = new PhasesHelper(HgInternals.getImplementationRepo(srcRepo));
		assertEquals("[sanity]", HgPhase.Draft, srcPhase.getPhase(r5, null));
		assertEquals("[sanity]", HgPhase.Secret, srcPhase.getPhase(r6, null));
		assertEquals("[sanity]", HgPhase.Draft, srcPhase.getPhase(r8, null));
		// change phases in repository of remote server:
		dstRun.exec("hg", "phase", "--public", String.valueOf(r5));
		assertEquals(0, dstRun.getExitValue());
		dstRun.exec("hg", "phase", "--draft", String.valueOf(r6));
		assertEquals(0, dstRun.getExitValue());
		dstRun.exec("hg", "phase", "--secret", "--force", String.valueOf(r8));
		assertEquals(0, dstRun.getExitValue());
		HgServer server = new HgServer().publishing(false).start(dstRepoLoc);
		try {
			final HgRemoteRepository dstRemote = hgLookup.detect(server.getURL());
			// commit new draft head
			new HgCheckoutCommand(srcRepo).changeset(publicCsetToBranchAt).clean(true).execute();
			RepoUtils.modifyFileAppend(f1, "// aaa");
			final HgCommitCommand commitCmd = new HgCommitCommand(srcRepo).message("Commit aaa");
			assertTrue(commitCmd.execute().isOk());
			final Nodeid newCommit = commitCmd.getCommittedRevision();
			//
			new HgPushCommand(srcRepo).destination(dstRemote).execute();
			// refresh phase information
			srcPhase = new PhasesHelper(HgInternals.getImplementationRepo(srcRepo));
			// r5 and r6 are changed to match server phases (more exposed)
			errorCollector.assertEquals(HgPhase.Public, srcPhase.getPhase(r5, null));
			errorCollector.assertEquals(HgPhase.Draft, srcPhase.getPhase(r6, null));
			// r8 is secret on server, locally can't make it less exposed though
			errorCollector.assertEquals(HgPhase.Draft, srcPhase.getPhase(r8, null));
			//
			HgRepository dstRepo = hgLookup.detect(dstRepoLoc);
			final HgChangelog dstClog = dstRepo.getChangelog();
			assertTrue(dstClog.isKnown(newCommit));
			PhasesHelper dstPhase = new PhasesHelper(HgInternals.getImplementationRepo(dstRepo));
			errorCollector.assertEquals(HgPhase.Draft, dstPhase.getPhase(dstClog.getRevisionIndex(newCommit), newCommit));
			// the one that was secret is draft now
			errorCollector.assertEquals(HgPhase.Draft, srcPhase.getPhase(r8, null));
		} finally {
			server.stop();
		}
	}
	
	/**
	 * update phases of local revisions and push changes
	 */
	@Test
	public void testPushPublishAndUpdates() throws Exception {
		File srcRepoLoc = RepoUtils.copyRepoToTempLocation("test-phases", "test-push-phase-update-2-src");
		File dstRepoLoc = RepoUtils.initEmptyTempRepo("test-push-phase-update-1-dst");
		final int r4 = 4, r5 = 5, r6 = 6, r9 = 9;
		HgServer server = new HgServer().publishing(false).start(dstRepoLoc);
		try {
			final HgLookup hgLookup = new HgLookup();
			final HgRepository srcRepo = hgLookup.detect(srcRepoLoc);
			final HgRemoteRepository dstRemote = hgLookup.detect(server.getURL());
			new HgPushCommand(srcRepo).destination(dstRemote).execute();
			//
			// make sure pushed repository got same draft root
			final Nodeid r4PublicHead = srcRepo.getChangelog().getRevision(r4);
			final Nodeid r5DraftRoot = srcRepo.getChangelog().getRevision(r5);
			HgRepository dstRepo = hgLookup.detect(dstRepoLoc);
			final HgChangelog dstClog = dstRepo.getChangelog();
			PhasesHelper dstPhase = new PhasesHelper(HgInternals.getImplementationRepo(dstRepo));
			assertEquals(HgPhase.Public, dstPhase.getPhase(dstClog.getRevisionIndex(r4PublicHead), r4PublicHead));
			assertEquals(HgPhase.Draft, dstPhase.getPhase(dstClog.getRevisionIndex(r5DraftRoot), r5DraftRoot));
			//
			// now, graduate some local revisions, r5:draft->public, r6:secret->public, r9: secret->draft
			final ExecHelper srcRun = new ExecHelper(new OutputParser.Stub(), srcRepoLoc);
			srcRun.exec("hg", "phase", "--public", String.valueOf(r5));
			srcRun.exec("hg", "phase", "--public", String.valueOf(r6));
			srcRun.exec("hg", "phase", "--draft", String.valueOf(r9));
			// PhaseHelper shall be new for the command, and would pick up these external changes 
			new HgPushCommand(srcRepo).destination(dstRemote).execute();
			final Nodeid r6Nodeid = srcRepo.getChangelog().getRevision(r6);
			final Nodeid r9Nodeid = srcRepo.getChangelog().getRevision(r9);
			// refresh 
			dstPhase = new PhasesHelper(HgInternals.getImplementationRepo(dstRepo));
			// not errorCollector as subsequent code would fail if these secret revs didn't get into dst
			assertTrue(dstClog.isKnown(r6Nodeid));
			assertTrue(dstClog.isKnown(r9Nodeid));
			errorCollector.assertEquals(HgPhase.Public, dstPhase.getPhase(dstClog.getRevisionIndex(r5DraftRoot), r5DraftRoot));
			errorCollector.assertEquals(HgPhase.Public, dstPhase.getPhase(dstClog.getRevisionIndex(r6Nodeid), r6Nodeid));
			errorCollector.assertEquals(HgPhase.Draft, dstPhase.getPhase(dstClog.getRevisionIndex(r9Nodeid), r9Nodeid));
		} finally {
			server.stop();
		}
	}

	
	/**
	 * XXX doesn't check the case when we push child of a draft revision which is
	 * known as public on server ((presentLocalDrafts \ outgoing) leaves bogus draft revision, 
	 * the parent one of the child added and pushed)
	 * For the time being, TestPull.testPullFromPublishing covers this case (as both push and
	 * pull share same phase update functionality)
	 */
	@Test
	public void testPushToPublishingServer() throws Exception {
		// copy, not clone as latter updates phase information
		File srcRepoLoc = RepoUtils.copyRepoToTempLocation("test-phases", "test-push-pub-src");
		File dstRepoLoc = RepoUtils.initEmptyTempRepo("test-push-pub-dst");
		HgServer server = new HgServer().publishing(true).start(dstRepoLoc);
		try {
			final HgLookup hgLookup = new HgLookup();
			final HgRepository srcRepo = hgLookup.detect(srcRepoLoc);
			final HgRemoteRepository dstRemote = hgLookup.detect(server.getURL());
			PhasesHelper phaseHelper = new PhasesHelper(HgInternals.getImplementationRepo(srcRepo));
			final RevisionSet allDraft = phaseHelper.allDraft();
			assertFalse("[sanity]", allDraft.isEmpty());
			// push all changes
			new HgPushCommand(srcRepo).destination(dstRemote).execute();
			HgRepository dstRepo = hgLookup.detect(dstRepoLoc);
			final HgChangelog srcClog = srcRepo.getChangelog();
			final HgChangelog dstClog = dstRepo.getChangelog();
			// refresh PhasesHelper
			phaseHelper = new PhasesHelper(HgInternals.getImplementationRepo(srcRepo));
			for (Nodeid n : allDraft) {
				// check drafts from src were actually pushed to dst 
				errorCollector.assertTrue(dstClog.isKnown(n));
				// check drafts became public
				errorCollector.assertEquals(HgPhase.Public, phaseHelper.getPhase(srcClog.getRevisionIndex(n), n));
			}
		} finally {
			server.stop();
		}
	}

	@Test
	public void testPushSecretChangesets() throws Exception {
		// copy, not clone as latter updates phase information
		File srcRepoLoc = RepoUtils.copyRepoToTempLocation("test-phases", "test-push-no-secret-src");
		File dstRepoLoc = RepoUtils.initEmptyTempRepo("test-push-no-secret-dst");
		HgServer server = new HgServer().start(dstRepoLoc);
		try {
			final HgLookup hgLookup = new HgLookup();
			final HgRepository srcRepo = hgLookup.detect(srcRepoLoc);
			final HgRemoteRepository dstRemote = hgLookup.detect(server.getURL());
			PhasesHelper phaseHelper = new PhasesHelper(HgInternals.getImplementationRepo(srcRepo));
			final RevisionSet allSecret = phaseHelper.allSecret();
			assertFalse("[sanity]", allSecret.isEmpty());
			new HgPushCommand(srcRepo).destination(dstRemote).execute();
			HgRepository dstRepo = hgLookup.detect(dstRepoLoc);
			final HgChangelog srcClog = srcRepo.getChangelog();
			final HgChangelog dstClog = dstRepo.getChangelog();
			errorCollector.assertEquals(srcClog.getRevisionCount() - allSecret.size(), dstClog.getRevisionCount());
			for (Nodeid n : allSecret) {		
				errorCollector.assertTrue(n.toString(), !dstClog.isKnown(n));
			}
		} finally {
			server.stop();
		}
	}

	@Test
	public void testUpdateBookmarkOnPush() throws Exception {
		File srcRepoLoc = RepoUtils.cloneRepoToTempLocation("test-annotate", "test-push-src", false);
		File dstRepoLoc = RepoUtils.cloneRepoToTempLocation("test-annotate", "test-push-dst", false);
		final ExecHelper srcRun = new ExecHelper(new OutputParser.Stub(), srcRepoLoc);
		final ExecHelper dstRun = new ExecHelper(new OutputParser.Stub(), dstRepoLoc);
		File f1 = new File(srcRepoLoc, "file1");
		assertTrue("[sanity]", f1.canWrite());
		//
		final String bm1 = "mark1", bm2 = "mark2", bm3 = "mark3", bm4 = "mark4", bm5 = "mark5";
		final int bm2Local = 1, bm2Remote = 6, bm3Local = 7, bm3Remote = 2, bm_4_5 = 3;
		// 1) bm1 - local active bookmark, check that push updates in remote
		srcRun.exec("hg", "bookmark", bm1);
		dstRun.exec("hg", "bookmark", "-r", "8", bm1);
		// 2) bm2 - local points to ancestor of revision remote points to
		srcRun.exec("hg", "bookmark", "-r", String.valueOf(bm2Local), bm2);
		dstRun.exec("hg", "bookmark", "-r", String.valueOf(bm2Remote), bm2);
		// 3) bm3 - remote points to ancestor of revision local one points to   
		srcRun.exec("hg", "bookmark", "-r", String.valueOf(bm3Local), bm3);
		dstRun.exec("hg", "bookmark", "-r", String.valueOf(bm3Remote), bm3);
		// 4) bm4 - remote bookmark, not known locally
		dstRun.exec("hg", "bookmark", "-r", String.valueOf(bm_4_5), bm4);
		// 5) bm5 - local bookmark, not known remotely
		srcRun.exec("hg", "bookmark", "-r", String.valueOf(bm_4_5), bm5);
		//
		HgServer server = new HgServer().start(dstRepoLoc);
		try {
			final HgLookup hgLookup = new HgLookup();
			final HgRepository srcRepo = hgLookup.detect(srcRepoLoc);
			final HgRemoteRepository dstRemote = hgLookup.detect(server.getURL());
			RepoUtils.modifyFileAppend(f1, "change1");
			final HgCommitCommand commitCmd = new HgCommitCommand(srcRepo).message("Commit 1");
			assertTrue(commitCmd.execute().isOk());
			assertEquals(bm1, srcRepo.getBookmarks().getActiveBookmarkName());
			assertEquals(commitCmd.getCommittedRevision(), srcRepo.getBookmarks().getRevision(bm1));
			//
			new HgPushCommand(srcRepo).destination(dstRemote).execute();
			Thread.sleep(300); // let the server perform the update
			//
			HgBookmarks srcBookmarks = srcRepo.getBookmarks();
			final HgChangelog srcClog = srcRepo.getChangelog();
			// first, check local bookmarks are intact
			errorCollector.assertEquals(srcClog.getRevision(bm2Local), srcBookmarks.getRevision(bm2));
			errorCollector.assertEquals(srcClog.getRevision(bm3Local), srcBookmarks.getRevision(bm3));
			errorCollector.assertEquals(null, srcBookmarks.getRevision(bm4));
			errorCollector.assertEquals(srcClog.getRevision(bm_4_5), srcBookmarks.getRevision(bm5));
			// now, check remote bookmarks were touched
			HgRepository dstRepo = hgLookup.detect(dstRepoLoc);
			HgBookmarks dstBookmarks = dstRepo.getBookmarks();
			final HgChangelog dstClog = dstRepo.getChangelog();
			// bm1 changed and points to newly pushed commit.
			// if the test fails (bm1 points to r8), chances are server didn't manage to update
			// bookmarks yet (there's Thread.sleep() above to give it a chance).
			errorCollector.assertEquals(commitCmd.getCommittedRevision(), dstBookmarks.getRevision(bm1));
			// bm2 didn't change
			errorCollector.assertEquals(dstClog.getRevision(bm2Remote), dstBookmarks.getRevision(bm2));
			// bm3 did change, now points to value we've got in srcRepo
			errorCollector.assertEquals(srcClog.getRevision(bm3Local), dstBookmarks.getRevision(bm3));
			// bm4 is not affected
			errorCollector.assertEquals(dstClog.getRevision(bm_4_5), dstBookmarks.getRevision(bm4));
			// bm5 is not known remotely
			errorCollector.assertEquals(null, dstBookmarks.getRevision(bm5));
		} finally {
			server.stop();
		}
	}

	private void checkRepositoriesAreSame(HgRepository srcRepo, HgRepository dstRepo) {
		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));
	}
}