view test/org/tmatesoft/hg/test/TestPush.java @ 709:497e697636fc

Report merged lines as changed block if possible, not as a sequence of added/deleted blocks. To facilitate access to merge parent lines AddBlock got mergeLineAt() method that reports index of the line in the second parent (if any), while insertedAt() has been changed to report index in the first parent always
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 21 Aug 2013 16:23:27 +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));
	}
}