View Javadoc
1   /*
2    * Copyright (C) 2016, Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.transport;
12  
13  import static org.junit.Assert.assertEquals;
14  import static org.junit.Assert.assertNull;
15  import static org.junit.Assert.assertSame;
16  import static org.junit.Assert.fail;
17  
18  import java.io.IOException;
19  import java.net.URISyntaxException;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.List;
23  
24  import org.eclipse.jgit.api.Git;
25  import org.eclipse.jgit.api.PushCommand;
26  import org.eclipse.jgit.api.errors.GitAPIException;
27  import org.eclipse.jgit.api.errors.NoFilepatternException;
28  import org.eclipse.jgit.api.errors.TransportException;
29  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
30  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
31  import org.eclipse.jgit.junit.RepositoryTestCase;
32  import org.eclipse.jgit.junit.TestRepository;
33  import org.eclipse.jgit.lib.NullProgressMonitor;
34  import org.eclipse.jgit.lib.ObjectId;
35  import org.eclipse.jgit.lib.Repository;
36  import org.eclipse.jgit.lib.StoredConfig;
37  import org.eclipse.jgit.revwalk.RevCommit;
38  import org.junit.After;
39  import org.junit.Before;
40  import org.junit.Test;
41  
42  public class PushOptionsTest extends RepositoryTestCase {
43  	private URIish uri;
44  	private TestProtocol<Object> testProtocol;
45  	private Object ctx = new Object();
46  	private InMemoryRepository server;
47  	private InMemoryRepository client;
48  	private ObjectId commit1;
49  	private ObjectId commit2;
50  	private ReceivePack receivePack;
51  
52  	@Override
53  	@Before
54  	public void setUp() throws Exception {
55  		super.setUp();
56  
57  		server = newRepo("server");
58  		client = newRepo("client");
59  
60  		testProtocol = new TestProtocol<>(null,
61  				(Object req, Repository git) -> {
62  					receivePack = new ReceivePack(git);
63  					receivePack.setAllowPushOptions(true);
64  					receivePack.setAtomic(true);
65  					return receivePack;
66  				});
67  
68  		uri = testProtocol.register(ctx, server);
69  
70  		try (TestRepository<?> clientRepo = new TestRepository<>(client)) {
71  			commit1 = clientRepo.commit().noFiles().message("test commit 1")
72  					.create();
73  			commit2 = clientRepo.commit().noFiles().message("test commit 2")
74  					.create();
75  		}
76  	}
77  
78  	@Override
79  	@After
80  	public void tearDown() {
81  		Transport.unregister(testProtocol);
82  	}
83  
84  	private static InMemoryRepository newRepo(String name) {
85  		return new InMemoryRepository(new DfsRepositoryDescription(name));
86  	}
87  
88  	private List<RemoteRefUpdate> commands(boolean atomicSafe)
89  			throws IOException {
90  		List<RemoteRefUpdate> cmds = new ArrayList<>();
91  		cmds.add(new RemoteRefUpdate(null, null, commit1, "refs/heads/one",
92  				true /* force update */, null /* no local tracking ref */,
93  				ObjectId.zeroId()));
94  		cmds.add(new RemoteRefUpdate(null, null, commit2, "refs/heads/two",
95  				true /* force update */, null /* no local tracking ref */,
96  				atomicSafe ? ObjectId.zeroId() : commit1));
97  		return cmds;
98  	}
99  
100 	private void connectLocalToRemote(Git local, Git remote)
101 			throws URISyntaxException, IOException {
102 		StoredConfig config = local.getRepository().getConfig();
103 		RemoteConfig remoteConfig = new RemoteConfig(config, "test");
104 		remoteConfig.addURI(new URIish(
105 				remote.getRepository().getDirectory().toURI().toURL()));
106 		remoteConfig.addFetchRefSpec(
107 				new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
108 		remoteConfig.update(config);
109 		config.save();
110 	}
111 
112 	private RevCommit addCommit(Git git)
113 			throws IOException, NoFilepatternException, GitAPIException {
114 		writeTrashFile("f", "content of f");
115 		git.add().addFilepattern("f").call();
116 		return git.commit().setMessage("adding f").call();
117 	}
118 
119 	@Test
120 	public void testNonAtomicPushWithOptions() throws Exception {
121 		PushResult r;
122 		server.setPerformsAtomicTransactions(false);
123 		List<String> pushOptions = Arrays.asList("Hello", "World!");
124 
125 		try (Transport tn = testProtocol.open(uri, client, "server")) {
126 			tn.setPushAtomic(false);
127 			tn.setPushOptions(pushOptions);
128 
129 			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
130 		}
131 
132 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
133 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
134 
135 		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
136 		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
137 				two.getStatus());
138 		assertEquals(pushOptions, receivePack.getPushOptions());
139 	}
140 
141 	@Test
142 	public void testAtomicPushWithOptions() throws Exception {
143 		PushResult r;
144 		server.setPerformsAtomicTransactions(true);
145 		List<String> pushOptions = Arrays.asList("Hello", "World!");
146 
147 		try (Transport tn = testProtocol.open(uri, client, "server")) {
148 			tn.setPushAtomic(true);
149 			tn.setPushOptions(pushOptions);
150 
151 			r = tn.push(NullProgressMonitor.INSTANCE, commands(true));
152 		}
153 
154 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
155 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
156 
157 		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
158 		assertSame(RemoteRefUpdate.Status.OK, two.getStatus());
159 		assertEquals(pushOptions, receivePack.getPushOptions());
160 	}
161 
162 	@Test
163 	public void testFailedAtomicPushWithOptions() throws Exception {
164 		PushResult r;
165 		server.setPerformsAtomicTransactions(true);
166 		List<String> pushOptions = Arrays.asList("Hello", "World!");
167 
168 		try (Transport tn = testProtocol.open(uri, client, "server")) {
169 			tn.setPushAtomic(true);
170 			tn.setPushOptions(pushOptions);
171 
172 			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
173 		}
174 
175 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
176 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
177 
178 		assertSame(RemoteRefUpdate.Status.REJECTED_OTHER_REASON,
179 				one.getStatus());
180 		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
181 				two.getStatus());
182 		assertNull(receivePack.getPushOptions());
183 	}
184 
185 	@Test
186 	public void testThinPushWithOptions() throws Exception {
187 		PushResult r;
188 		List<String> pushOptions = Arrays.asList("Hello", "World!");
189 
190 		try (Transport tn = testProtocol.open(uri, client, "server")) {
191 			tn.setPushThin(true);
192 			tn.setPushOptions(pushOptions);
193 
194 			r = tn.push(NullProgressMonitor.INSTANCE, commands(false));
195 		}
196 
197 		RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one");
198 		RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two");
199 
200 		assertSame(RemoteRefUpdate.Status.OK, one.getStatus());
201 		assertSame(RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED,
202 				two.getStatus());
203 		assertEquals(pushOptions, receivePack.getPushOptions());
204 	}
205 
206 	@Test
207 	public void testPushWithoutOptions() throws Exception {
208 		try (Git local = new Git(db);
209 				Git remote = new Git(createBareRepository())) {
210 			connectLocalToRemote(local, remote);
211 
212 			final StoredConfig config2 = remote.getRepository().getConfig();
213 			config2.setBoolean("receive", null, "pushoptions", true);
214 			config2.save();
215 
216 			RevCommit commit = addCommit(local);
217 
218 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
219 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
220 
221 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
222 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
223 			assertNull(remote.getRepository().resolve("refs/heads/master"));
224 
225 			PushCommand pushCommand = local.push().setRemote("test");
226 			pushCommand.call();
227 
228 			assertEquals(commit.getId(),
229 					remote.getRepository().resolve("refs/heads/branchtopush"));
230 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
231 			assertNull(remote.getRepository().resolve("refs/heads/master"));
232 		}
233 	}
234 
235 	@Test
236 	public void testPushWithEmptyOptions() throws Exception {
237 		try (Git local = new Git(db);
238 				Git remote = new Git(createBareRepository())) {
239 			connectLocalToRemote(local, remote);
240 
241 			final StoredConfig config2 = remote.getRepository().getConfig();
242 			config2.setBoolean("receive", null, "pushoptions", true);
243 			config2.save();
244 
245 			RevCommit commit = addCommit(local);
246 
247 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
248 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
249 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
250 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
251 			assertNull(remote.getRepository().resolve("refs/heads/master"));
252 
253 			List<String> pushOptions = new ArrayList<>();
254 			PushCommand pushCommand = local.push().setRemote("test")
255 					.setPushOptions(pushOptions);
256 			pushCommand.call();
257 
258 			assertEquals(commit.getId(),
259 					remote.getRepository().resolve("refs/heads/branchtopush"));
260 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
261 			assertNull(remote.getRepository().resolve("refs/heads/master"));
262 		}
263 	}
264 
265 	@Test
266 	public void testAdvertisedButUnusedPushOptions() throws Exception {
267 		try (Git local = new Git(db);
268 				Git remote = new Git(createBareRepository())) {
269 			connectLocalToRemote(local, remote);
270 
271 			final StoredConfig config2 = remote.getRepository().getConfig();
272 			config2.setBoolean("receive", null, "pushoptions", true);
273 			config2.save();
274 
275 			RevCommit commit = addCommit(local);
276 
277 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
278 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
279 
280 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
281 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
282 			assertNull(remote.getRepository().resolve("refs/heads/master"));
283 
284 			PushCommand pushCommand = local.push().setRemote("test")
285 					.setPushOptions(null);
286 			pushCommand.call();
287 
288 			assertEquals(commit.getId(),
289 					remote.getRepository().resolve("refs/heads/branchtopush"));
290 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
291 			assertNull(remote.getRepository().resolve("refs/heads/master"));
292 		}
293 	}
294 
295 	@Test(expected = TransportException.class)
296 	public void testPushOptionsNotSupported() throws Exception {
297 		try (Git local = new Git(db);
298 				Git remote = new Git(createBareRepository())) {
299 			connectLocalToRemote(local, remote);
300 
301 			final StoredConfig config2 = remote.getRepository().getConfig();
302 			config2.setBoolean("receive", null, "pushoptions", false);
303 			config2.save();
304 
305 			addCommit(local);
306 
307 			local.checkout().setName("not-pushed").setCreateBranch(true).call();
308 			local.checkout().setName("branchtopush").setCreateBranch(true).call();
309 
310 			assertNull(remote.getRepository().resolve("refs/heads/branchtopush"));
311 			assertNull(remote.getRepository().resolve("refs/heads/not-pushed"));
312 			assertNull(remote.getRepository().resolve("refs/heads/master"));
313 
314 			List<String> pushOptions = new ArrayList<>();
315 			PushCommand pushCommand = local.push().setRemote("test")
316 					.setPushOptions(pushOptions);
317 			pushCommand.call();
318 
319 			fail("should already have thrown TransportException");
320 		}
321 	}
322 }