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