View Javadoc
1   /*
2    * Copyright (C) 2022 Nail Samatov <sanail@yandex.ru> 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  package org.eclipse.jgit.lfs.internal;
11  
12  import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
13  import static org.junit.Assert.assertEquals;
14  import static org.junit.Assert.assertThrows;
15  import static org.junit.Assert.assertTrue;
16  
17  import java.io.File;
18  import java.io.IOException;
19  import java.io.PrintWriter;
20  import java.io.StringWriter;
21  
22  import org.eclipse.jgit.api.Git;
23  import org.eclipse.jgit.api.RemoteAddCommand;
24  import org.eclipse.jgit.attributes.FilterCommandRegistry;
25  import org.eclipse.jgit.junit.RepositoryTestCase;
26  import org.eclipse.jgit.lfs.CleanFilter;
27  import org.eclipse.jgit.lfs.Protocol;
28  import org.eclipse.jgit.lfs.SmudgeFilter;
29  import org.eclipse.jgit.lfs.errors.LfsConfigInvalidException;
30  import org.eclipse.jgit.lfs.lib.Constants;
31  import org.eclipse.jgit.lib.ConfigConstants;
32  import org.eclipse.jgit.lib.Repository;
33  import org.eclipse.jgit.lib.StoredConfig;
34  import org.eclipse.jgit.transport.URIish;
35  import org.eclipse.jgit.transport.http.HttpConnection;
36  import org.eclipse.jgit.util.HttpSupport;
37  import org.junit.AfterClass;
38  import org.junit.Before;
39  import org.junit.BeforeClass;
40  import org.junit.Test;
41  
42  public class LfsConnectionFactoryTest extends RepositoryTestCase {
43  
44  	private static final String SMUDGE_NAME = org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
45  			+ Constants.ATTR_FILTER_DRIVER_PREFIX
46  			+ org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_SMUDGE;
47  
48  	private static final String CLEAN_NAME = org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
49  			+ Constants.ATTR_FILTER_DRIVER_PREFIX
50  			+ org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_CLEAN;
51  
52  	private final static String LFS_SERVER_URL1 = "https://lfs.server1/test/uri";
53  
54  	private final static String LFS_SERVER_URL2 = "https://lfs.server2/test/uri";
55  
56  	private final static String ORIGIN_URL = "https://git.server/test/uri";
57  
58  	private Git git;
59  
60  	@BeforeClass
61  	public static void installLfs() {
62  		FilterCommandRegistry.register(SMUDGE_NAME, SmudgeFilter.FACTORY);
63  		FilterCommandRegistry.register(CLEAN_NAME, CleanFilter.FACTORY);
64  	}
65  
66  	@AfterClass
67  	public static void removeLfs() {
68  		FilterCommandRegistry.unregister(SMUDGE_NAME);
69  		FilterCommandRegistry.unregister(CLEAN_NAME);
70  	}
71  
72  	@Override
73  	@Before
74  	public void setUp() throws Exception {
75  		super.setUp();
76  		git = new Git(db);
77  
78  		// Just to have a non empty repo
79  		writeTrashFile("Test.txt", "Hello world from the LFS Factory Test");
80  		git.add().addFilepattern("Test.txt").call();
81  		git.commit().setMessage("Initial commit").call();
82  	}
83  
84  	@Test
85  	public void lfsUrlFromRemoteUrlWithDotGit() throws Exception {
86  		addRemoteUrl("https://localhost/repo.git");
87  		checkLfsUrl("https://localhost/repo.git/info/lfs");
88  	}
89  
90  	@Test
91  	public void lfsUrlFromRemoteUrlWithoutDotGit() throws Exception {
92  		addRemoteUrl("https://localhost/repo");
93  		checkLfsUrl("https://localhost/repo.git/info/lfs");
94  	}
95  
96  	@Test
97  	public void lfsUrlFromLocalConfig() throws Exception {
98  		addRemoteUrl("https://localhost/repo");
99  
100 		StoredConfig cfg = ((Repository) db).getConfig();
101 		cfg.setString(ConfigConstants.CONFIG_SECTION_LFS,
102 				null,
103 				ConfigConstants.CONFIG_KEY_URL,
104 				"https://localhost/repo/lfs");
105 		cfg.save();
106 
107 		checkLfsUrl("https://localhost/repo/lfs");
108 	}
109 
110 	@Test
111 	public void lfsUrlFromOriginConfig() throws Exception {
112 		addRemoteUrl("https://localhost/repo");
113 
114 		StoredConfig cfg = ((Repository) db).getConfig();
115 		cfg.setString(ConfigConstants.CONFIG_SECTION_LFS,
116 				org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME,
117 				ConfigConstants.CONFIG_KEY_URL,
118 				"https://localhost/repo/lfs");
119 		cfg.save();
120 
121 		checkLfsUrl("https://localhost/repo/lfs");
122 	}
123 
124 	@Test
125 	public void lfsUrlNotConfigured() throws Exception {
126 		assertThrows(LfsConfigInvalidException.class,
127 				() -> LfsConnectionFactory.getLfsConnection(db,
128 				HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD));
129 	}
130 
131 	@Test
132 	public void checkGetLfsConnection_lfsurl_lfsconfigFromWorkingDir()
133 			throws Exception {
134 		writeLfsConfig();
135 		checkLfsUrl(LFS_SERVER_URL1);
136 	}
137 
138 	@Test
139 	public void checkGetLfsConnection_lfsurl_lfsconfigFromIndex()
140 			throws Exception {
141 		writeLfsConfig();
142 		git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
143 		deleteTrashFile(Constants.DOT_LFS_CONFIG);
144 		checkLfsUrl(LFS_SERVER_URL1);
145 	}
146 
147 	@Test
148 	public void checkGetLfsConnection_lfsurl_lfsconfigFromHEAD()
149 			throws Exception {
150 		writeLfsConfig();
151 		git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
152 		git.commit().setMessage("Commit LFS Config").call();
153 
154 		/*
155 		 * reading .lfsconfig from HEAD seems only testable using a bare repo,
156 		 * since otherwise working tree or index are used
157 		 */
158 		File directory = createTempDirectory("testBareRepo");
159 		try (Repository bareRepoDb = Git.cloneRepository()
160 				.setDirectory(directory)
161 				.setURI(db.getDirectory().toURI().toString()).setBare(true)
162 				.call().getRepository()) {
163 
164 			checkLfsUrl(LFS_SERVER_URL1);
165 		}
166 	}
167 
168 	@Test
169 	public void checkGetLfsConnection_remote_lfsconfigFromWorkingDir()
170 			throws Exception {
171 		addRemoteUrl(ORIGIN_URL);
172 		writeLfsConfig(LFS_SERVER_URL1, "lfs", DEFAULT_REMOTE_NAME, "url");
173 		checkLfsUrl(LFS_SERVER_URL1);
174 	}
175 
176 	/**
177 	 * Test the config file precedence.
178 	 *
179 	 * Checking only with the local repository config is sufficient since from
180 	 * that point the "normal" precedence is used.
181 	 *
182 	 * @throws Exception
183 	 */
184 	@Test
185 	public void checkGetLfsConnection_ConfigFilePrecedence_lfsconfigFromWorkingDir()
186 			throws Exception {
187 		writeLfsConfig();
188 		checkLfsUrl(LFS_SERVER_URL1);
189 
190 		StoredConfig config = git.getRepository().getConfig();
191 		config.setString(ConfigConstants.CONFIG_SECTION_LFS, null,
192 				ConfigConstants.CONFIG_KEY_URL, LFS_SERVER_URL2);
193 		config.save();
194 
195 		checkLfsUrl(LFS_SERVER_URL2);
196 	}
197 
198 	@Test
199 	public void checkGetLfsConnection_InvalidLfsConfig_WorkingDir()
200 			throws Exception {
201 		writeInvalidLfsConfig();
202 		LfsConfigInvalidException actualException = assertThrows(
203 				LfsConfigInvalidException.class, () -> {
204 			LfsConnectionFactory.getLfsConnection(db, HttpSupport.METHOD_POST,
205 					Protocol.OPERATION_DOWNLOAD);
206 		});
207 		assertTrue(getStackTrace(actualException)
208 				.contains("Invalid line in config file"));
209 	}
210 
211 	@Test
212 	public void checkGetLfsConnection_InvalidLfsConfig_Index()
213 			throws Exception {
214 		writeInvalidLfsConfig();
215 		git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
216 		deleteTrashFile(Constants.DOT_LFS_CONFIG);
217 		LfsConfigInvalidException actualException = assertThrows(
218 				LfsConfigInvalidException.class, () -> {
219 			LfsConnectionFactory.getLfsConnection(db, HttpSupport.METHOD_POST,
220 					Protocol.OPERATION_DOWNLOAD);
221 		});
222 		assertTrue(getStackTrace(actualException)
223 				.contains("Invalid line in config file"));
224 	}
225 
226 	@Test
227 	public void checkGetLfsConnection_InvalidLfsConfig_HEAD() throws Exception {
228 		writeInvalidLfsConfig();
229 		git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
230 		git.commit().setMessage("Commit LFS Config").call();
231 
232 		/*
233 		 * reading .lfsconfig from HEAD seems only testable using a bare repo,
234 		 * since otherwise working tree or index are used
235 		 */
236 		File directory = createTempDirectory("testBareRepo");
237 		try (Repository bareRepoDb = Git.cloneRepository()
238 				.setDirectory(directory)
239 				.setURI(db.getDirectory().toURI().toString()).setBare(true)
240 				.call().getRepository()) {
241 			LfsConfigInvalidException actualException = assertThrows(
242 					LfsConfigInvalidException.class,
243 					() -> {
244 						LfsConnectionFactory.getLfsConnection(db,
245 								HttpSupport.METHOD_POST,
246 								Protocol.OPERATION_DOWNLOAD);
247 					});
248 			assertTrue(getStackTrace(actualException)
249 					.contains("Invalid line in config file"));
250 		}
251 	}
252 
253 	private void addRemoteUrl(String remotUrl) throws Exception {
254 		RemoteAddCommand add = git.remoteAdd();
255 		add.setUri(new URIish(remotUrl));
256 		add.setName(org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME);
257 		add.call();
258 	}
259 
260 	/**
261 	 * Returns the stack trace of the provided exception as string
262 	 *
263 	 * @param actualException
264 	 * @return The exception stack trace as string
265 	 */
266 	private String getStackTrace(Exception actualException) {
267 		StringWriter sw = new StringWriter();
268 		PrintWriter pw = new PrintWriter(sw);
269 		actualException.printStackTrace(pw);
270 		return sw.toString();
271 	}
272 
273 	private void writeLfsConfig() throws IOException {
274 		writeLfsConfig(LFS_SERVER_URL1, "lfs", "url");
275 	}
276 
277 	private void writeLfsConfig(String lfsUrl, String section, String name)
278 			throws IOException {
279 		writeLfsConfig(lfsUrl, section, null, name);
280 	}
281 
282 	/*
283 	 * Write simple lfs config with single entry. Do not use FileBasedConfig to
284 	 * avoid introducing new dependency (for now).
285 	 */
286 	private void writeLfsConfig(String lfsUrl, String section,
287 			String subsection, String name) throws IOException {
288 		StringBuilder config = new StringBuilder();
289 		config.append("[");
290 		config.append(section);
291 		if (subsection != null) {
292 			config.append(" \"");
293 			config.append(subsection);
294 			config.append("\"");
295 		}
296 		config.append("]\n");
297 		config.append("    ");
298 		config.append(name);
299 		config.append(" = ");
300 		config.append(lfsUrl);
301 		writeTrashFile(Constants.DOT_LFS_CONFIG, config.toString());
302 	}
303 
304 	private void writeInvalidLfsConfig() throws IOException {
305 		writeTrashFile(Constants.DOT_LFS_CONFIG,
306 				"{lfs]\n    url = " + LFS_SERVER_URL1);
307 	}
308 
309 	private void checkLfsUrl(String lfsUrl) throws IOException {
310 		HttpConnection lfsServerConn;
311 		lfsServerConn = LfsConnectionFactory.getLfsConnection(db,
312 				HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD);
313 
314 		assertEquals(lfsUrl + Protocol.OBJECTS_LFS_ENDPOINT,
315 				lfsServerConn.getURL().toString());
316 	}
317 }