View Javadoc
1   /*
2    * Copyright (C) 2016, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.transport;
45  
46  import static org.junit.Assert.assertEquals;
47  import static org.junit.Assert.assertNull;
48  import static org.junit.Assert.assertSame;
49  import static org.junit.Assert.fail;
50  
51  import java.io.IOException;
52  import java.net.URISyntaxException;
53  import java.util.ArrayList;
54  import java.util.Arrays;
55  import java.util.List;
56  
57  import org.eclipse.jgit.api.Git;
58  import org.eclipse.jgit.api.PushCommand;
59  import org.eclipse.jgit.api.errors.GitAPIException;
60  import org.eclipse.jgit.api.errors.NoFilepatternException;
61  import org.eclipse.jgit.api.errors.TransportException;
62  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
63  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
64  import org.eclipse.jgit.junit.RepositoryTestCase;
65  import org.eclipse.jgit.lib.Constants;
66  import org.eclipse.jgit.lib.NullProgressMonitor;
67  import org.eclipse.jgit.lib.ObjectId;
68  import org.eclipse.jgit.lib.ObjectInserter;
69  import org.eclipse.jgit.lib.Repository;
70  import org.eclipse.jgit.lib.StoredConfig;
71  import org.eclipse.jgit.revwalk.RevCommit;
72  import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
73  import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
74  import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
75  import org.junit.After;
76  import org.junit.Before;
77  import org.junit.Test;
78  
79  public class PushOptionsTest extends RepositoryTestCase {
80  	private URIish uri;
81  	private TestProtocol<Object> testProtocol;
82  	private Object ctx = new Object();
83  	private InMemoryRepository server;
84  	private InMemoryRepository client;
85  	private ObjectId obj1;
86  	private ObjectId obj2;
87  	private ReceivePack receivePack;
88  
89  	@Override
90  	@Before
91  	public void setUp() throws Exception {
92  		super.setUp();
93  
94  		server = newRepo("server");
95  		client = newRepo("client");
96  
97  		testProtocol = new TestProtocol<>(null,
98  				new ReceivePackFactory<Object>() {
99  					@Override
100 					public ReceivePack create(Object req, Repository git)
101 							throws ServiceNotEnabledException,
102 							ServiceNotAuthorizedException {
103 						receivePack = new ReceivePack(git);
104 						receivePack.setAllowPushOptions(true);
105 						receivePack.setAtomic(true);
106 						return receivePack;
107 					}
108 				});
109 
110 		uri = testProtocol.register(ctx, server);
111 
112 		try (ObjectInserter ins = client.newObjectInserter()) {
113 			obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test"));
114 			obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file"));
115 			ins.flush();
116 		}
117 	}
118 
119 	@Override
120 	@After
121 	public void tearDown() {
122 		Transport.unregister(testProtocol);
123 	}
124 
125 	private static InMemoryRepository newRepo(String name) {
126 		return new InMemoryRepository(new DfsRepositoryDescription(name));
127 	}
128 
129 	private List<RemoteRefUpdate> commands(boolean atomicSafe)
130 			throws IOException {
131 		List<RemoteRefUpdate> cmds = new ArrayList<>();
132 		cmds.add(new RemoteRefUpdate(null, null, obj1, "refs/heads/one",
133 				true /* force update */, null /* no local tracking ref */,
134 				ObjectId.zeroId()));
135 		cmds.add(new RemoteRefUpdate(null, null, obj2, "refs/heads/two",
136 				true /* force update */, null /* no local tracking ref */,
137 				atomicSafe ? ObjectId.zeroId() : obj1));
138 		return cmds;
139 	}
140 
141 	private void connectLocalToRemote(Git local, Git remote)
142 			throws URISyntaxException, IOException {
143 		StoredConfig config = local.getRepository().getConfig();
144 		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
145 		remoteConfig.addURI(new URIish(
146 				remote.getRepository().getDirectory().toURI().toURL()));
147 		remoteConfig.addFetchRefSpec(
148 				new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
149 		remoteConfig.update(config);
150 		config.save();
151 	}
152 
153 	private RevCommit addCommit(Git git)
154 			throws IOException, NoFilepatternException, GitAPIException {
155 		writeTrashFile("f", "content of f");
156 		git.add().addFilepattern("f").call();
157 		return git.commit().setMessage("adding f").call();
158 	}
159 
160 	@Test
161 	public void testNonAtomicPushWithOptions() throws Exception {
162 		PushResult r;
163 		server.setPerformsAtomicTransactions(false);
164 		List<String> pushOptions = Arrays.asList("Hello", "World!");
165 
166 		try (Transport tn = testProtocol.open(uri, client, "server")) {
167 			tn.setPushAtomic(false);
168 			tn.setPushOptions(pushOptions);
169 
170 			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
171 		}
172 
173 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
174 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
175 
176 		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
177 		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
178 				two.getStatus());
179 		assertEquals(pushOptions, receivePack.getPushOptions());
180 	}
181 
182 	@Test
183 	public void testAtomicPushWithOptions() throws Exception {
184 		PushResult r;
185 		server.setPerformsAtomicTransactions(true);
186 		List<String> pushOptions = Arrays.asList("Hello", "World!");
187 
188 		try (Transport tn = testProtocol.open(uri, client, "server")) {
189 			tn.setPushAtomic(true);
190 			tn.setPushOptions(pushOptions);
191 
192 			r = tn.push(NullProgressMonitor.INSTANCE, commands(true));
193 		}
194 
195 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
196 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
197 
198 		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
199 		assertSame(RemoteRefUpdate.Status.OK, two.getStatus());
200 		assertEquals(pushOptions, receivePack.getPushOptions());
201 	}
202 
203 	@Test
204 	public void testFailedAtomicPushWithOptions() throws Exception {
205 		PushResult r;
206 		server.setPerformsAtomicTransactions(true);
207 		List<String> pushOptions = Arrays.asList("Hello", "World!");
208 
209 		try (Transport tn = testProtocol.open(uri, client, "server")) {
210 			tn.setPushAtomic(true);
211 			tn.setPushOptions(pushOptions);
212 
213 			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
214 		}
215 
216 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
217 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
218 
219 		assertSame(RemoteRefUpdate.Status.REJECTED_OTHER_REASON,
220 				one.getStatus());
221 		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
222 				two.getStatus());
223 		assertNull(receivePack.getPushOptions());
224 	}
225 
226 	@Test
227 	public void testThinPushWithOptions() throws Exception {
228 		PushResult r;
229 		List<String> pushOptions = Arrays.asList("Hello", "World!");
230 
231 		try (Transport tn = testProtocol.open(uri, client, "server")) {
232 			tn.setPushThin(true);
233 			tn.setPushOptions(pushOptions);
234 
235 			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
236 		}
237 
238 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
239 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
240 
241 		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
242 		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
243 				two.getStatus());
244 		assertEquals(pushOptions, receivePack.getPushOptions());
245 	}
246 
247 	@Test
248 	public void testPushWithoutOptions() throws Exception {
249 		try (Git local = new Git(db);
250 				Git remote = new Git(createBareRepository())) {
251 			connectLocalToRemote(local, remote);
252 
253 			final StoredConfig config2 = remote.getRepository().getConfig();
254 			config2.setBoolean("receive", null, "pushoptions", true);
255 			config2.save();
256 
257 			RevCommit commit = addCommit(local);
258 
259 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
260 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
261 
262 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
263 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
264 			assertNull(remote.getRepository().resolve("refs/heads/master"));
265 
266 			PushCommand pushCommand = local.push().setRemote("test");
267 			pushCommand.call();
268 
269 			assertEquals(commit.getId(),
270 					remote.getRepository().resolve("refs/heads/branchtopush"));
271 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
272 			assertNull(remote.getRepository().resolve("refs/heads/master"));
273 		}
274 	}
275 
276 	@Test
277 	public void testPushWithEmptyOptions() throws Exception {
278 		try (Git local = new Git(db);
279 				Git remote = new Git(createBareRepository())) {
280 			connectLocalToRemote(local, remote);
281 
282 			final StoredConfig config2 = remote.getRepository().getConfig();
283 			config2.setBoolean("receive", null, "pushoptions", true);
284 			config2.save();
285 
286 			RevCommit commit = addCommit(local);
287 
288 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
289 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
290 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
291 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
292 			assertNull(remote.getRepository().resolve("refs/heads/master"));
293 
294 			List<String> pushOptions = new ArrayList<>();
295 			PushCommand pushCommand = local.push().setRemote("test")
296 					.setPushOptions(pushOptions);
297 			pushCommand.call();
298 
299 			assertEquals(commit.getId(),
300 					remote.getRepository().resolve("refs/heads/branchtopush"));
301 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
302 			assertNull(remote.getRepository().resolve("refs/heads/master"));
303 		}
304 	}
305 
306 	@Test
307 	public void testAdvertisedButUnusedPushOptions() throws Exception {
308 		try (Git local = new Git(db);
309 				Git remote = new Git(createBareRepository())) {
310 			connectLocalToRemote(local, remote);
311 
312 			final StoredConfig config2 = remote.getRepository().getConfig();
313 			config2.setBoolean("receive", null, "pushoptions", true);
314 			config2.save();
315 
316 			RevCommit commit = addCommit(local);
317 
318 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
319 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
320 
321 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
322 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
323 			assertNull(remote.getRepository().resolve("refs/heads/master"));
324 
325 			PushCommand pushCommand = local.push().setRemote("test")
326 					.setPushOptions(null);
327 			pushCommand.call();
328 
329 			assertEquals(commit.getId(),
330 					remote.getRepository().resolve("refs/heads/branchtopush"));
331 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
332 			assertNull(remote.getRepository().resolve("refs/heads/master"));
333 		}
334 	}
335 
336 	@Test(expected = TransportException.class)
337 	public void testPushOptionsNotSupported() throws Exception {
338 		try (Git local = new Git(db);
339 				Git remote = new Git(createBareRepository())) {
340 			connectLocalToRemote(local, remote);
341 
342 			final StoredConfig config2 = remote.getRepository().getConfig();
343 			config2.setBoolean("receive", null, "pushoptions", false);
344 			config2.save();
345 
346 			addCommit(local);
347 
348 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
349 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
350 
351 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
352 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
353 			assertNull(remote.getRepository().resolve("refs/heads/master"));
354 
355 			List<String> pushOptions = new ArrayList<>();
356 			PushCommand pushCommand = local.push().setRemote("test")
357 					.setPushOptions(pushOptions);
358 			pushCommand.call();
359 
360 			fail("should already have thrown TransportException");
361 		}
362 	}
363 }