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  	@Before
90  	public void setUp() throws Exception {
91  		super.setUp();
92  
93  		server = newRepo("server");
94  		client = newRepo("client");
95  
96  		testProtocol = new TestProtocol<>(null,
97  				new ReceivePackFactory<Object>() {
98  					@Override
99  					public ReceivePack create(Object req, Repository git)
100 							throws ServiceNotEnabledException,
101 							ServiceNotAuthorizedException {
102 						receivePack = new ReceivePack(git);
103 						receivePack.setAllowPushOptions(true);
104 						receivePack.setAtomic(true);
105 						return receivePack;
106 					}
107 				});
108 
109 		uri = testProtocol.register(ctx, server);
110 
111 		try (ObjectInserter ins = client.newObjectInserter()) {
112 			obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test"));
113 			obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file"));
114 			ins.flush();
115 		}
116 	}
117 
118 	@After
119 	public void tearDown() {
120 		Transport.unregister(testProtocol);
121 	}
122 
123 	private static InMemoryRepository newRepo(String name) {
124 		return new InMemoryRepository(new DfsRepositoryDescription(name));
125 	}
126 
127 	private List<RemoteRefUpdate> commands(boolean atomicSafe)
128 			throws IOException {
129 		List<RemoteRefUpdate> cmds = new ArrayList<>();
130 		cmds.add(new RemoteRefUpdate(null, null, obj1, "refs/heads/one",
131 				true /* force update */, null /* no local tracking ref */,
132 				ObjectId.zeroId()));
133 		cmds.add(new RemoteRefUpdate(null, null, obj2, "refs/heads/two",
134 				true /* force update */, null /* no local tracking ref */,
135 				atomicSafe ? ObjectId.zeroId() : obj1));
136 		return cmds;
137 	}
138 
139 	private void connectLocalToRemote(Git local, Git remote)
140 			throws URISyntaxException, IOException {
141 		StoredConfig config = local.getRepository().getConfig();
142 		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
143 		remoteConfig.addURI(new URIish(
144 				remote.getRepository().getDirectory().toURI().toURL()));
145 		remoteConfig.addFetchRefSpec(
146 				new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
147 		remoteConfig.update(config);
148 		config.save();
149 	}
150 
151 	private RevCommit addCommit(Git git)
152 			throws IOException, NoFilepatternException, GitAPIException {
153 		writeTrashFile("f", "content of f");
154 		git.add().addFilepattern("f").call();
155 		return git.commit().setMessage("adding f").call();
156 	}
157 
158 	@Test
159 	public void testNonAtomicPushWithOptions() throws Exception {
160 		PushResult r;
161 		server.setPerformsAtomicTransactions(false);
162 		List<String> pushOptions = Arrays.asList("Hello", "World!");
163 
164 		try (Transport tn = testProtocol.open(uri, client, "server")) {
165 			tn.setPushAtomic(false);
166 			tn.setPushOptions(pushOptions);
167 
168 			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
169 		}
170 
171 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
172 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
173 
174 		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
175 		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
176 				two.getStatus());
177 		assertEquals(pushOptions, receivePack.getPushOptions());
178 	}
179 
180 	@Test
181 	public void testAtomicPushWithOptions() throws Exception {
182 		PushResult r;
183 		server.setPerformsAtomicTransactions(true);
184 		List<String> pushOptions = Arrays.asList("Hello", "World!");
185 
186 		try (Transport tn = testProtocol.open(uri, client, "server")) {
187 			tn.setPushAtomic(true);
188 			tn.setPushOptions(pushOptions);
189 
190 			r = tn.push(NullProgressMonitor.INSTANCE, commands(true));
191 		}
192 
193 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
194 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
195 
196 		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
197 		assertSame(RemoteRefUpdate.Status.OK, two.getStatus());
198 		assertEquals(pushOptions, receivePack.getPushOptions());
199 	}
200 
201 	@Test
202 	public void testFailedAtomicPushWithOptions() throws Exception {
203 		PushResult r;
204 		server.setPerformsAtomicTransactions(true);
205 		List<String> pushOptions = Arrays.asList("Hello", "World!");
206 
207 		try (Transport tn = testProtocol.open(uri, client, "server")) {
208 			tn.setPushAtomic(true);
209 			tn.setPushOptions(pushOptions);
210 
211 			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
212 		}
213 
214 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
215 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
216 
217 		assertSame(RemoteRefUpdate.Status.REJECTED_OTHER_REASON,
218 				one.getStatus());
219 		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
220 				two.getStatus());
221 		assertNull(receivePack.getPushOptions());
222 	}
223 
224 	@Test
225 	public void testThinPushWithOptions() throws Exception {
226 		PushResult r;
227 		List<String> pushOptions = Arrays.asList("Hello", "World!");
228 
229 		try (Transport tn = testProtocol.open(uri, client, "server")) {
230 			tn.setPushThin(true);
231 			tn.setPushOptions(pushOptions);
232 
233 			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
234 		}
235 
236 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
237 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
238 
239 		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
240 		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
241 				two.getStatus());
242 		assertEquals(pushOptions, receivePack.getPushOptions());
243 	}
244 
245 	@Test
246 	public void testPushWithoutOptions() throws Exception {
247 		try (Git local = new Git(db);
248 				Git remote = new Git(createBareRepository())) {
249 			connectLocalToRemote(local, remote);
250 
251 			final StoredConfig config2 = remote.getRepository().getConfig();
252 			config2.setBoolean("receive", null, "pushoptions", true);
253 			config2.save();
254 
255 			RevCommit commit = addCommit(local);
256 
257 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
258 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
259 
260 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
261 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
262 			assertNull(remote.getRepository().resolve("refs/heads/master"));
263 
264 			PushCommand pushCommand = local.push().setRemote("test");
265 			pushCommand.call();
266 
267 			assertEquals(commit.getId(),
268 					remote.getRepository().resolve("refs/heads/branchtopush"));
269 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
270 			assertNull(remote.getRepository().resolve("refs/heads/master"));
271 		}
272 	}
273 
274 	@Test
275 	public void testPushWithEmptyOptions() throws Exception {
276 		try (Git local = new Git(db);
277 				Git remote = new Git(createBareRepository())) {
278 			connectLocalToRemote(local, remote);
279 
280 			final StoredConfig config2 = remote.getRepository().getConfig();
281 			config2.setBoolean("receive", null, "pushoptions", true);
282 			config2.save();
283 
284 			RevCommit commit = addCommit(local);
285 
286 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
287 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
288 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
289 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
290 			assertNull(remote.getRepository().resolve("refs/heads/master"));
291 
292 			List<String> pushOptions = new ArrayList<>();
293 			PushCommand pushCommand = local.push().setRemote("test")
294 					.setPushOptions(pushOptions);
295 			pushCommand.call();
296 
297 			assertEquals(commit.getId(),
298 					remote.getRepository().resolve("refs/heads/branchtopush"));
299 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
300 			assertNull(remote.getRepository().resolve("refs/heads/master"));
301 		}
302 	}
303 
304 	@Test
305 	public void testAdvertisedButUnusedPushOptions() throws Exception {
306 		try (Git local = new Git(db);
307 				Git remote = new Git(createBareRepository())) {
308 			connectLocalToRemote(local, remote);
309 
310 			final StoredConfig config2 = remote.getRepository().getConfig();
311 			config2.setBoolean("receive", null, "pushoptions", true);
312 			config2.save();
313 
314 			RevCommit commit = addCommit(local);
315 
316 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
317 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
318 
319 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
320 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
321 			assertNull(remote.getRepository().resolve("refs/heads/master"));
322 
323 			PushCommand pushCommand = local.push().setRemote("test")
324 					.setPushOptions(null);
325 			pushCommand.call();
326 
327 			assertEquals(commit.getId(),
328 					remote.getRepository().resolve("refs/heads/branchtopush"));
329 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
330 			assertNull(remote.getRepository().resolve("refs/heads/master"));
331 		}
332 	}
333 
334 	@Test(expected = TransportException.class)
335 	public void testPushOptionsNotSupported() throws Exception {
336 		try (Git local = new Git(db);
337 				Git remote = new Git(createBareRepository())) {
338 			connectLocalToRemote(local, remote);
339 
340 			final StoredConfig config2 = remote.getRepository().getConfig();
341 			config2.setBoolean("receive", null, "pushoptions", false);
342 			config2.save();
343 
344 			addCommit(local);
345 
346 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
347 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
348 
349 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
350 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
351 			assertNull(remote.getRepository().resolve("refs/heads/master"));
352 
353 			List<String> pushOptions = new ArrayList<>();
354 			PushCommand pushCommand = local.push().setRemote("test")
355 					.setPushOptions(pushOptions);
356 			pushCommand.call();
357 
358 			fail("should already have thrown TransportException");
359 		}
360 	}
361 }