xref: /JGit/org.eclipse.jgit.junit.ssh/src/org/eclipse/jgit/junit/ssh/SshTestHarness.java (revision 566e49d7d39b12c785be24b8b61b4960a4b7ea17)
18d2d6836SMatthias Sohn /*
2eb67862cSThomas Wolf  * Copyright (C) 2018, 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
38d2d6836SMatthias Sohn  *
48d2d6836SMatthias Sohn  * This program and the accompanying materials are made available under the
58d2d6836SMatthias Sohn  * terms of the Eclipse Distribution License v. 1.0 which is available at
68d2d6836SMatthias Sohn  * https://www.eclipse.org/org/documents/edl-v10.php.
78d2d6836SMatthias Sohn  *
88d2d6836SMatthias Sohn  * SPDX-License-Identifier: BSD-3-Clause
98d2d6836SMatthias Sohn  */
108d2d6836SMatthias Sohn package org.eclipse.jgit.junit.ssh;
118d2d6836SMatthias Sohn 
128d2d6836SMatthias Sohn import static org.junit.Assert.assertEquals;
138d2d6836SMatthias Sohn import static org.junit.Assert.assertFalse;
148d2d6836SMatthias Sohn import static org.junit.Assert.assertNotEquals;
158d2d6836SMatthias Sohn import static org.junit.Assert.assertNotNull;
168d2d6836SMatthias Sohn import static org.junit.Assert.assertTrue;
178d2d6836SMatthias Sohn 
18eb67862cSThomas Wolf import java.io.BufferedWriter;
198d2d6836SMatthias Sohn import java.io.File;
208d2d6836SMatthias Sohn import java.io.FileOutputStream;
218d2d6836SMatthias Sohn import java.io.IOException;
228d2d6836SMatthias Sohn import java.io.InputStream;
238d2d6836SMatthias Sohn import java.io.OutputStream;
24eb67862cSThomas Wolf import java.nio.charset.StandardCharsets;
258d2d6836SMatthias Sohn import java.nio.file.Files;
26eb67862cSThomas Wolf import java.security.KeyPair;
27eb67862cSThomas Wolf import java.security.KeyPairGenerator;
28eb67862cSThomas Wolf import java.security.PrivateKey;
298d2d6836SMatthias Sohn import java.util.ArrayList;
308d2d6836SMatthias Sohn import java.util.Arrays;
31eb67862cSThomas Wolf import java.util.Base64;
328d2d6836SMatthias Sohn import java.util.Collections;
338d2d6836SMatthias Sohn import java.util.Iterator;
348d2d6836SMatthias Sohn import java.util.List;
358d2d6836SMatthias Sohn 
36eb67862cSThomas Wolf import org.apache.sshd.common.config.keys.PublicKeyEntry;
378d2d6836SMatthias Sohn import org.eclipse.jgit.api.CloneCommand;
388d2d6836SMatthias Sohn import org.eclipse.jgit.api.Git;
398d2d6836SMatthias Sohn import org.eclipse.jgit.api.PushCommand;
408d2d6836SMatthias Sohn import org.eclipse.jgit.api.ResetCommand.ResetType;
418d2d6836SMatthias Sohn import org.eclipse.jgit.errors.UnsupportedCredentialItem;
428d2d6836SMatthias Sohn import org.eclipse.jgit.junit.RepositoryTestCase;
438d2d6836SMatthias Sohn import org.eclipse.jgit.lib.Constants;
448d2d6836SMatthias Sohn import org.eclipse.jgit.lib.Repository;
458d2d6836SMatthias Sohn import org.eclipse.jgit.revwalk.RevCommit;
468d2d6836SMatthias Sohn import org.eclipse.jgit.transport.CredentialItem;
478d2d6836SMatthias Sohn import org.eclipse.jgit.transport.CredentialsProvider;
488d2d6836SMatthias Sohn import org.eclipse.jgit.transport.PushResult;
498d2d6836SMatthias Sohn import org.eclipse.jgit.transport.RemoteRefUpdate;
508d2d6836SMatthias Sohn import org.eclipse.jgit.transport.SshSessionFactory;
518d2d6836SMatthias Sohn import org.eclipse.jgit.transport.URIish;
528d2d6836SMatthias Sohn import org.eclipse.jgit.util.FS;
538d2d6836SMatthias Sohn import org.junit.After;
548d2d6836SMatthias Sohn 
558d2d6836SMatthias Sohn /**
568d2d6836SMatthias Sohn  * Root class for ssh tests. Sets up the ssh test server. A set of pre-computed
578d2d6836SMatthias Sohn  * keys for testing is provided in the bundle and can be used in test cases via
588d2d6836SMatthias Sohn  * {@link #copyTestResource(String, File)}. These test key files names have four
598d2d6836SMatthias Sohn  * components, separated by a single underscore: "id", the algorithm, the bits
608d2d6836SMatthias Sohn  * (if variable), and the password if the private key is encrypted. For instance
618d2d6836SMatthias Sohn  * "{@code id_ecdsa_384_testpass}" is an encrypted ECDSA-384 key. The passphrase
628d2d6836SMatthias Sohn  * to decrypt is "testpass". The key "{@code id_ecdsa_384}" is the same but
638d2d6836SMatthias Sohn  * unencrypted. All keys were generated and encrypted via ssh-keygen. Note that
648d2d6836SMatthias Sohn  * DSA and ec25519 have no "bits" component. Available keys are listed in
658d2d6836SMatthias Sohn  * {@link SshTestBase#KEY_RESOURCES}.
668d2d6836SMatthias Sohn  */
678d2d6836SMatthias Sohn public abstract class SshTestHarness extends RepositoryTestCase {
688d2d6836SMatthias Sohn 
698d2d6836SMatthias Sohn 	protected static final String TEST_USER = "testuser";
708d2d6836SMatthias Sohn 
718d2d6836SMatthias Sohn 	protected File sshDir;
728d2d6836SMatthias Sohn 
738d2d6836SMatthias Sohn 	protected File privateKey1;
748d2d6836SMatthias Sohn 
758d2d6836SMatthias Sohn 	protected File privateKey2;
768d2d6836SMatthias Sohn 
778d2d6836SMatthias Sohn 	protected File publicKey1;
788d2d6836SMatthias Sohn 
79*566e49d7SThomas Wolf 	protected File publicKey2;
80*566e49d7SThomas Wolf 
818d2d6836SMatthias Sohn 	protected SshTestGitServer server;
828d2d6836SMatthias Sohn 
838d2d6836SMatthias Sohn 	private SshSessionFactory factory;
848d2d6836SMatthias Sohn 
858d2d6836SMatthias Sohn 	protected int testPort;
868d2d6836SMatthias Sohn 
878d2d6836SMatthias Sohn 	protected File knownHosts;
888d2d6836SMatthias Sohn 
898d2d6836SMatthias Sohn 	private File homeDir;
908d2d6836SMatthias Sohn 
918d2d6836SMatthias Sohn 	@Override
setUp()928d2d6836SMatthias Sohn 	public void setUp() throws Exception {
938d2d6836SMatthias Sohn 		super.setUp();
948d2d6836SMatthias Sohn 		writeTrashFile("file.txt", "something");
958d2d6836SMatthias Sohn 		try (Git git = new Git(db)) {
968d2d6836SMatthias Sohn 			git.add().addFilepattern("file.txt").call();
978d2d6836SMatthias Sohn 			git.commit().setMessage("Initial commit").call();
988d2d6836SMatthias Sohn 		}
998d2d6836SMatthias Sohn 		mockSystemReader.setProperty("user.home",
1008d2d6836SMatthias Sohn 				getTemporaryDirectory().getAbsolutePath());
1018d2d6836SMatthias Sohn 		mockSystemReader.setProperty("HOME",
1028d2d6836SMatthias Sohn 				getTemporaryDirectory().getAbsolutePath());
1038d2d6836SMatthias Sohn 		homeDir = FS.DETECTED.userHome();
1048d2d6836SMatthias Sohn 		FS.DETECTED.setUserHome(getTemporaryDirectory().getAbsoluteFile());
1058d2d6836SMatthias Sohn 		sshDir = new File(getTemporaryDirectory(), ".ssh");
1068d2d6836SMatthias Sohn 		assertTrue(sshDir.mkdir());
1078d2d6836SMatthias Sohn 		File serverDir = new File(getTemporaryDirectory(), "srv");
1088d2d6836SMatthias Sohn 		assertTrue(serverDir.mkdir());
1098d2d6836SMatthias Sohn 		// Create two key pairs. Let's not call them "id_rsa".
110eb67862cSThomas Wolf 		KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
111eb67862cSThomas Wolf 		generator.initialize(2048);
1128d2d6836SMatthias Sohn 		privateKey1 = new File(sshDir, "first_key");
1138d2d6836SMatthias Sohn 		privateKey2 = new File(sshDir, "second_key");
114eb67862cSThomas Wolf 		publicKey1 = createKeyPair(generator.generateKeyPair(), privateKey1);
115*566e49d7SThomas Wolf 		publicKey2 = createKeyPair(generator.generateKeyPair(), privateKey2);
116eb67862cSThomas Wolf 		// Create a host key
117eb67862cSThomas Wolf 		KeyPair hostKey = generator.generateKeyPair();
1188d2d6836SMatthias Sohn 		// Start a server with our test user and the first key.
1198d2d6836SMatthias Sohn 		server = new SshTestGitServer(TEST_USER, publicKey1.toPath(), db,
120eb67862cSThomas Wolf 				hostKey);
1218d2d6836SMatthias Sohn 		testPort = server.start();
1228d2d6836SMatthias Sohn 		assertTrue(testPort > 0);
1238d2d6836SMatthias Sohn 		knownHosts = new File(sshDir, "known_hosts");
124eb67862cSThomas Wolf 		StringBuilder knownHostsLine = new StringBuilder();
125eb67862cSThomas Wolf 		knownHostsLine.append("[localhost]:").append(testPort).append(' ');
126eb67862cSThomas Wolf 		PublicKeyEntry.appendPublicKeyEntry(knownHostsLine,
127eb67862cSThomas Wolf 				hostKey.getPublic());
128eb67862cSThomas Wolf 		Files.write(knownHosts.toPath(),
129eb67862cSThomas Wolf 				Collections.singleton(knownHostsLine.toString()));
1308d2d6836SMatthias Sohn 		factory = createSessionFactory();
1318d2d6836SMatthias Sohn 		SshSessionFactory.setInstance(factory);
1328d2d6836SMatthias Sohn 	}
1338d2d6836SMatthias Sohn 
createKeyPair(KeyPair newKey, File privateKeyFile)134eb67862cSThomas Wolf 	private static File createKeyPair(KeyPair newKey, File privateKeyFile)
135eb67862cSThomas Wolf 			throws Exception {
136eb67862cSThomas Wolf 		// Write PKCS#8 PEM unencrypted. Both JSch and sshd can read that.
137eb67862cSThomas Wolf 		PrivateKey privateKey = newKey.getPrivate();
138eb67862cSThomas Wolf 		String format = privateKey.getFormat();
139eb67862cSThomas Wolf 		if (!"PKCS#8".equalsIgnoreCase(format)) {
140eb67862cSThomas Wolf 			throw new IOException("Cannot write " + privateKey.getAlgorithm()
141eb67862cSThomas Wolf 					+ " key in " + format + " format");
142eb67862cSThomas Wolf 		}
143eb67862cSThomas Wolf 		try (BufferedWriter writer = Files.newBufferedWriter(
144eb67862cSThomas Wolf 				privateKeyFile.toPath(), StandardCharsets.US_ASCII)) {
145eb67862cSThomas Wolf 			writer.write("-----BEGIN PRIVATE KEY-----");
146eb67862cSThomas Wolf 			writer.newLine();
147eb67862cSThomas Wolf 			write(writer, privateKey.getEncoded(), 64);
148eb67862cSThomas Wolf 			writer.write("-----END PRIVATE KEY-----");
149eb67862cSThomas Wolf 			writer.newLine();
1508d2d6836SMatthias Sohn 		}
1518d2d6836SMatthias Sohn 		File publicKeyFile = new File(privateKeyFile.getParentFile(),
1528d2d6836SMatthias Sohn 				privateKeyFile.getName() + ".pub");
153eb67862cSThomas Wolf 		StringBuilder builder = new StringBuilder();
154eb67862cSThomas Wolf 		PublicKeyEntry.appendPublicKeyEntry(builder, newKey.getPublic());
155eb67862cSThomas Wolf 		builder.append(' ').append(TEST_USER);
1568d2d6836SMatthias Sohn 		try (OutputStream out = new FileOutputStream(publicKeyFile)) {
157eb67862cSThomas Wolf 			out.write(builder.toString().getBytes(StandardCharsets.US_ASCII));
1588d2d6836SMatthias Sohn 		}
1598d2d6836SMatthias Sohn 		return publicKeyFile;
1608d2d6836SMatthias Sohn 	}
1618d2d6836SMatthias Sohn 
write(BufferedWriter out, byte[] bytes, int lineLength)162eb67862cSThomas Wolf 	private static void write(BufferedWriter out, byte[] bytes, int lineLength)
163eb67862cSThomas Wolf 			throws IOException {
164eb67862cSThomas Wolf 		String data = Base64.getEncoder().encodeToString(bytes);
165eb67862cSThomas Wolf 		int last = data.length();
166eb67862cSThomas Wolf 		for (int i = 0; i < last; i += lineLength) {
167eb67862cSThomas Wolf 			if (i + lineLength <= last) {
168eb67862cSThomas Wolf 				out.write(data.substring(i, i + lineLength));
169eb67862cSThomas Wolf 			} else {
170eb67862cSThomas Wolf 				out.write(data.substring(i));
1718d2d6836SMatthias Sohn 			}
172eb67862cSThomas Wolf 			out.newLine();
173eb67862cSThomas Wolf 		}
174eb67862cSThomas Wolf 		Arrays.fill(bytes, (byte) 0);
1758d2d6836SMatthias Sohn 	}
1768d2d6836SMatthias Sohn 
1778d2d6836SMatthias Sohn 	/**
1788d2d6836SMatthias Sohn 	 * Creates a new known_hosts file with one entry for the given host and port
1798d2d6836SMatthias Sohn 	 * taken from the given public key file.
1808d2d6836SMatthias Sohn 	 *
1818d2d6836SMatthias Sohn 	 * @param file
1828d2d6836SMatthias Sohn 	 *            to write the known_hosts file to
1838d2d6836SMatthias Sohn 	 * @param host
1848d2d6836SMatthias Sohn 	 *            for the entry
1858d2d6836SMatthias Sohn 	 * @param port
1868d2d6836SMatthias Sohn 	 *            for the entry
1878d2d6836SMatthias Sohn 	 * @param publicKey
1888d2d6836SMatthias Sohn 	 *            to use
1898d2d6836SMatthias Sohn 	 * @return the public-key part of the line
1908d2d6836SMatthias Sohn 	 * @throws IOException
1918d2d6836SMatthias Sohn 	 */
createKnownHostsFile(File file, String host, int port, File publicKey)1928d2d6836SMatthias Sohn 	protected static String createKnownHostsFile(File file, String host,
1938d2d6836SMatthias Sohn 			int port, File publicKey) throws IOException {
194eb67862cSThomas Wolf 		List<String> lines = Files.readAllLines(publicKey.toPath(),
195eb67862cSThomas Wolf 				StandardCharsets.UTF_8);
1968d2d6836SMatthias Sohn 		assertEquals("Public key has too many lines", 1, lines.size());
1978d2d6836SMatthias Sohn 		String pubKey = lines.get(0);
1988d2d6836SMatthias Sohn 		// Strip off the comment.
1998d2d6836SMatthias Sohn 		String[] parts = pubKey.split("\\s+");
2008d2d6836SMatthias Sohn 		assertTrue("Unexpected key content",
2018d2d6836SMatthias Sohn 				parts.length == 2 || parts.length == 3);
2028d2d6836SMatthias Sohn 		String keyPart = parts[0] + ' ' + parts[1];
2038d2d6836SMatthias Sohn 		String line = '[' + host + "]:" + port + ' ' + keyPart;
2048d2d6836SMatthias Sohn 		Files.write(file.toPath(), Collections.singletonList(line));
2058d2d6836SMatthias Sohn 		return keyPart;
2068d2d6836SMatthias Sohn 	}
2078d2d6836SMatthias Sohn 
2088d2d6836SMatthias Sohn 	/**
2098d2d6836SMatthias Sohn 	 * Checks whether there is a line for the given host and port that also
2108d2d6836SMatthias Sohn 	 * matches the given key part in the list of lines.
2118d2d6836SMatthias Sohn 	 *
2128d2d6836SMatthias Sohn 	 * @param host
2138d2d6836SMatthias Sohn 	 *            to look for
2148d2d6836SMatthias Sohn 	 * @param port
2158d2d6836SMatthias Sohn 	 *            to look for
2168d2d6836SMatthias Sohn 	 * @param keyPart
2178d2d6836SMatthias Sohn 	 *            to look for
2188d2d6836SMatthias Sohn 	 * @param lines
2198d2d6836SMatthias Sohn 	 *            to look in
2208d2d6836SMatthias Sohn 	 * @return {@code true} if found, {@code false} otherwise
2218d2d6836SMatthias Sohn 	 */
hasHostKey(String host, int port, String keyPart, List<String> lines)2228d2d6836SMatthias Sohn 	protected boolean hasHostKey(String host, int port, String keyPart,
2238d2d6836SMatthias Sohn 			List<String> lines) {
2248d2d6836SMatthias Sohn 		String h = '[' + host + "]:" + port;
2258d2d6836SMatthias Sohn 		return lines.stream()
2268d2d6836SMatthias Sohn 				.anyMatch(l -> l.contains(h) && l.contains(keyPart));
2278d2d6836SMatthias Sohn 	}
2288d2d6836SMatthias Sohn 
2298d2d6836SMatthias Sohn 	@After
shutdownServer()2308d2d6836SMatthias Sohn 	public void shutdownServer() throws Exception {
2318d2d6836SMatthias Sohn 		if (server != null) {
2328d2d6836SMatthias Sohn 			server.stop();
2338d2d6836SMatthias Sohn 			server = null;
2348d2d6836SMatthias Sohn 		}
2358d2d6836SMatthias Sohn 		FS.DETECTED.setUserHome(homeDir);
2368d2d6836SMatthias Sohn 		SshSessionFactory.setInstance(null);
2378d2d6836SMatthias Sohn 		factory = null;
2388d2d6836SMatthias Sohn 	}
2398d2d6836SMatthias Sohn 
createSessionFactory()2408d2d6836SMatthias Sohn 	protected abstract SshSessionFactory createSessionFactory();
2418d2d6836SMatthias Sohn 
getSessionFactory()2428d2d6836SMatthias Sohn 	protected SshSessionFactory getSessionFactory() {
2438d2d6836SMatthias Sohn 		return factory;
2448d2d6836SMatthias Sohn 	}
2458d2d6836SMatthias Sohn 
installConfig(String... config)2468d2d6836SMatthias Sohn 	protected abstract void installConfig(String... config);
2478d2d6836SMatthias Sohn 
2488d2d6836SMatthias Sohn 	/**
2498d2d6836SMatthias Sohn 	 * Copies a test data file contained in the test bundle to the given file.
2508d2d6836SMatthias Sohn 	 * Equivalent to {@link #copyTestResource(Class, String, File)} with
2518d2d6836SMatthias Sohn 	 * {@code SshTestHarness.class} as first parameter.
2528d2d6836SMatthias Sohn 	 *
2538d2d6836SMatthias Sohn 	 * @param resourceName
2548d2d6836SMatthias Sohn 	 *            of the test resource to copy
2558d2d6836SMatthias Sohn 	 * @param to
2568d2d6836SMatthias Sohn 	 *            file to copy the resource to
2578d2d6836SMatthias Sohn 	 * @throws IOException
2588d2d6836SMatthias Sohn 	 *             if the resource cannot be copied
2598d2d6836SMatthias Sohn 	 */
copyTestResource(String resourceName, File to)2608d2d6836SMatthias Sohn 	protected void copyTestResource(String resourceName, File to)
2618d2d6836SMatthias Sohn 			throws IOException {
2628d2d6836SMatthias Sohn 		copyTestResource(SshTestHarness.class, resourceName, to);
2638d2d6836SMatthias Sohn 	}
2648d2d6836SMatthias Sohn 
2658d2d6836SMatthias Sohn 	/**
2668d2d6836SMatthias Sohn 	 * Copies a test data file contained in the test bundle to the given file,
2678d2d6836SMatthias Sohn 	 * using {@link Class#getResourceAsStream(String)} to get the test resource.
2688d2d6836SMatthias Sohn 	 *
2698d2d6836SMatthias Sohn 	 * @param loader
2708d2d6836SMatthias Sohn 	 *            {@link Class} to use to load the resource
2718d2d6836SMatthias Sohn 	 * @param resourceName
2728d2d6836SMatthias Sohn 	 *            of the test resource to copy
2738d2d6836SMatthias Sohn 	 * @param to
2748d2d6836SMatthias Sohn 	 *            file to copy the resource to
2758d2d6836SMatthias Sohn 	 * @throws IOException
2768d2d6836SMatthias Sohn 	 *             if the resource cannot be copied
2778d2d6836SMatthias Sohn 	 */
copyTestResource(Class<?> loader, String resourceName, File to)2788d2d6836SMatthias Sohn 	protected void copyTestResource(Class<?> loader, String resourceName,
2798d2d6836SMatthias Sohn 			File to) throws IOException {
2808d2d6836SMatthias Sohn 		try (InputStream in = loader.getResourceAsStream(resourceName)) {
2818d2d6836SMatthias Sohn 			Files.copy(in, to.toPath());
2828d2d6836SMatthias Sohn 		}
2838d2d6836SMatthias Sohn 	}
2848d2d6836SMatthias Sohn 
cloneWith(String uri, File to, CredentialsProvider provider, String... config)2858d2d6836SMatthias Sohn 	protected File cloneWith(String uri, File to, CredentialsProvider provider,
2868d2d6836SMatthias Sohn 			String... config) throws Exception {
2878d2d6836SMatthias Sohn 		installConfig(config);
2888d2d6836SMatthias Sohn 		CloneCommand clone = Git.cloneRepository().setCloneAllBranches(true)
2898d2d6836SMatthias Sohn 				.setDirectory(to).setURI(uri);
2908d2d6836SMatthias Sohn 		if (provider != null) {
2918d2d6836SMatthias Sohn 			clone.setCredentialsProvider(provider);
2928d2d6836SMatthias Sohn 		}
2938d2d6836SMatthias Sohn 		try (Git git = clone.call()) {
2948d2d6836SMatthias Sohn 			Repository repo = git.getRepository();
2958d2d6836SMatthias Sohn 			assertNotNull(repo.resolve("master"));
2968d2d6836SMatthias Sohn 			assertNotEquals(db.getWorkTree(),
2978d2d6836SMatthias Sohn 					git.getRepository().getWorkTree());
2988d2d6836SMatthias Sohn 			assertTrue(new File(git.getRepository().getWorkTree(), "file.txt")
2998d2d6836SMatthias Sohn 					.exists());
3008d2d6836SMatthias Sohn 			return repo.getWorkTree();
3018d2d6836SMatthias Sohn 		}
3028d2d6836SMatthias Sohn 	}
3038d2d6836SMatthias Sohn 
pushTo(File localClone)3048d2d6836SMatthias Sohn 	protected void pushTo(File localClone) throws Exception {
3058d2d6836SMatthias Sohn 		pushTo(null, localClone);
3068d2d6836SMatthias Sohn 	}
3078d2d6836SMatthias Sohn 
pushTo(CredentialsProvider provider, File localClone)3088d2d6836SMatthias Sohn 	protected void pushTo(CredentialsProvider provider, File localClone)
3098d2d6836SMatthias Sohn 			throws Exception {
3108d2d6836SMatthias Sohn 		RevCommit commit;
3118d2d6836SMatthias Sohn 		File newFile = null;
3128d2d6836SMatthias Sohn 		try (Git git = Git.open(localClone)) {
3138d2d6836SMatthias Sohn 			// Write a new file and modify a file.
3148d2d6836SMatthias Sohn 			Repository local = git.getRepository();
3158d2d6836SMatthias Sohn 			newFile = File.createTempFile("new", "sshtest",
3168d2d6836SMatthias Sohn 					local.getWorkTree());
3178d2d6836SMatthias Sohn 			write(newFile, "something new");
3188d2d6836SMatthias Sohn 			File existingFile = new File(local.getWorkTree(), "file.txt");
3198d2d6836SMatthias Sohn 			write(existingFile, "something else");
3208d2d6836SMatthias Sohn 			git.add().addFilepattern("file.txt")
3218d2d6836SMatthias Sohn 					.addFilepattern(newFile.getName())
3228d2d6836SMatthias Sohn 					.call();
3238d2d6836SMatthias Sohn 			commit = git.commit().setMessage("Local commit").call();
3248d2d6836SMatthias Sohn 			// Push
3258d2d6836SMatthias Sohn 			PushCommand push = git.push().setPushAll();
3268d2d6836SMatthias Sohn 			if (provider != null) {
3278d2d6836SMatthias Sohn 				push.setCredentialsProvider(provider);
3288d2d6836SMatthias Sohn 			}
3298d2d6836SMatthias Sohn 			Iterable<PushResult> results = push.call();
3308d2d6836SMatthias Sohn 			for (PushResult result : results) {
3318d2d6836SMatthias Sohn 				for (RemoteRefUpdate u : result.getRemoteUpdates()) {
3328d2d6836SMatthias Sohn 					assertEquals(
3338d2d6836SMatthias Sohn 							"Could not update " + u.getRemoteName() + ' '
3348d2d6836SMatthias Sohn 									+ u.getMessage(),
3358d2d6836SMatthias Sohn 							RemoteRefUpdate.Status.OK, u.getStatus());
3368d2d6836SMatthias Sohn 				}
3378d2d6836SMatthias Sohn 			}
3388d2d6836SMatthias Sohn 		}
3398d2d6836SMatthias Sohn 		// Now check "master" in the remote repo directly:
3408d2d6836SMatthias Sohn 		assertEquals("Unexpected remote commit", commit, db.resolve("master"));
3418d2d6836SMatthias Sohn 		assertEquals("Unexpected remote commit", commit,
3428d2d6836SMatthias Sohn 				db.resolve(Constants.HEAD));
3438d2d6836SMatthias Sohn 		File remoteFile = new File(db.getWorkTree(), newFile.getName());
3448d2d6836SMatthias Sohn 		assertFalse("File should not exist on remote", remoteFile.exists());
3458d2d6836SMatthias Sohn 		try (Git git = new Git(db)) {
3468d2d6836SMatthias Sohn 			git.reset().setMode(ResetType.HARD).setRef(Constants.HEAD).call();
3478d2d6836SMatthias Sohn 		}
3488d2d6836SMatthias Sohn 		assertTrue("File does not exist on remote", remoteFile.exists());
3498d2d6836SMatthias Sohn 		checkFile(remoteFile, "something new");
3508d2d6836SMatthias Sohn 	}
3518d2d6836SMatthias Sohn 
3528d2d6836SMatthias Sohn 	protected static class TestCredentialsProvider extends CredentialsProvider {
3538d2d6836SMatthias Sohn 
3548d2d6836SMatthias Sohn 		private final List<String> stringStore;
3558d2d6836SMatthias Sohn 
3568d2d6836SMatthias Sohn 		private final Iterator<String> strings;
3578d2d6836SMatthias Sohn 
TestCredentialsProvider(String... strings)3588d2d6836SMatthias Sohn 		public TestCredentialsProvider(String... strings) {
3598d2d6836SMatthias Sohn 			if (strings == null || strings.length == 0) {
3608d2d6836SMatthias Sohn 				stringStore = Collections.emptyList();
3618d2d6836SMatthias Sohn 			} else {
3628d2d6836SMatthias Sohn 				stringStore = Arrays.asList(strings);
3638d2d6836SMatthias Sohn 			}
3648d2d6836SMatthias Sohn 			this.strings = stringStore.iterator();
3658d2d6836SMatthias Sohn 		}
3668d2d6836SMatthias Sohn 
3678d2d6836SMatthias Sohn 		@Override
isInteractive()3688d2d6836SMatthias Sohn 		public boolean isInteractive() {
3698d2d6836SMatthias Sohn 			return true;
3708d2d6836SMatthias Sohn 		}
3718d2d6836SMatthias Sohn 
3728d2d6836SMatthias Sohn 		@Override
supports(CredentialItem... items)3738d2d6836SMatthias Sohn 		public boolean supports(CredentialItem... items) {
3748d2d6836SMatthias Sohn 			return true;
3758d2d6836SMatthias Sohn 		}
3768d2d6836SMatthias Sohn 
3778d2d6836SMatthias Sohn 		@Override
get(URIish uri, CredentialItem... items)3788d2d6836SMatthias Sohn 		public boolean get(URIish uri, CredentialItem... items)
3798d2d6836SMatthias Sohn 				throws UnsupportedCredentialItem {
3808d2d6836SMatthias Sohn 			System.out.println("URI: " + uri);
3818d2d6836SMatthias Sohn 			for (CredentialItem item : items) {
3828d2d6836SMatthias Sohn 				System.out.println(item.getClass().getSimpleName() + ' '
3838d2d6836SMatthias Sohn 						+ item.getPromptText());
3848d2d6836SMatthias Sohn 			}
3858d2d6836SMatthias Sohn 			logItems(uri, items);
3868d2d6836SMatthias Sohn 			for (CredentialItem item : items) {
3878d2d6836SMatthias Sohn 				if (item instanceof CredentialItem.InformationalMessage) {
3888d2d6836SMatthias Sohn 					continue;
3898d2d6836SMatthias Sohn 				}
3908d2d6836SMatthias Sohn 				if (item instanceof CredentialItem.YesNoType) {
3918d2d6836SMatthias Sohn 					((CredentialItem.YesNoType) item).setValue(true);
3928d2d6836SMatthias Sohn 				} else if (item instanceof CredentialItem.CharArrayType) {
3938d2d6836SMatthias Sohn 					if (strings.hasNext()) {
3948d2d6836SMatthias Sohn 						((CredentialItem.CharArrayType) item)
3958d2d6836SMatthias Sohn 								.setValue(strings.next().toCharArray());
3968d2d6836SMatthias Sohn 					} else {
3978d2d6836SMatthias Sohn 						return false;
3988d2d6836SMatthias Sohn 					}
3998d2d6836SMatthias Sohn 				} else if (item instanceof CredentialItem.StringType) {
4008d2d6836SMatthias Sohn 					if (strings.hasNext()) {
4018d2d6836SMatthias Sohn 						((CredentialItem.StringType) item)
4028d2d6836SMatthias Sohn 								.setValue(strings.next());
4038d2d6836SMatthias Sohn 					} else {
4048d2d6836SMatthias Sohn 						return false;
4058d2d6836SMatthias Sohn 					}
4068d2d6836SMatthias Sohn 				} else {
4078d2d6836SMatthias Sohn 					return false;
4088d2d6836SMatthias Sohn 				}
4098d2d6836SMatthias Sohn 			}
4108d2d6836SMatthias Sohn 			return true;
4118d2d6836SMatthias Sohn 		}
4128d2d6836SMatthias Sohn 
4138d2d6836SMatthias Sohn 		private List<LogEntry> log = new ArrayList<>();
4148d2d6836SMatthias Sohn 
logItems(URIish uri, CredentialItem... items)4158d2d6836SMatthias Sohn 		private void logItems(URIish uri, CredentialItem... items) {
4168d2d6836SMatthias Sohn 			log.add(new LogEntry(uri, Arrays.asList(items)));
4178d2d6836SMatthias Sohn 		}
4188d2d6836SMatthias Sohn 
getLog()4198d2d6836SMatthias Sohn 		public List<LogEntry> getLog() {
4208d2d6836SMatthias Sohn 			return log;
4218d2d6836SMatthias Sohn 		}
4228d2d6836SMatthias Sohn 	}
4238d2d6836SMatthias Sohn 
4248d2d6836SMatthias Sohn 	protected static class LogEntry {
4258d2d6836SMatthias Sohn 
4268d2d6836SMatthias Sohn 		private URIish uri;
4278d2d6836SMatthias Sohn 
4288d2d6836SMatthias Sohn 		private List<CredentialItem> items;
4298d2d6836SMatthias Sohn 
LogEntry(URIish uri, List<CredentialItem> items)4308d2d6836SMatthias Sohn 		public LogEntry(URIish uri, List<CredentialItem> items) {
4318d2d6836SMatthias Sohn 			this.uri = uri;
4328d2d6836SMatthias Sohn 			this.items = items;
4338d2d6836SMatthias Sohn 		}
4348d2d6836SMatthias Sohn 
getURIish()4358d2d6836SMatthias Sohn 		public URIish getURIish() {
4368d2d6836SMatthias Sohn 			return uri;
4378d2d6836SMatthias Sohn 		}
4388d2d6836SMatthias Sohn 
getItems()4398d2d6836SMatthias Sohn 		public List<CredentialItem> getItems() {
4408d2d6836SMatthias Sohn 			return items;
4418d2d6836SMatthias Sohn 		}
4428d2d6836SMatthias Sohn 	}
4438d2d6836SMatthias Sohn }
444