View Javadoc
1   /*
2    * Copyright (C) 2022, Matthias Fromme <mfromme@dspace.de>
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;
11  
12  import static org.junit.Assert.assertEquals;
13  import static org.junit.Assert.assertTrue;
14  
15  import java.io.File;
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.OutputStream;
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  import org.eclipse.jgit.api.Git;
23  import org.eclipse.jgit.api.ResetCommand.ResetType;
24  import org.eclipse.jgit.attributes.FilterCommand;
25  import org.eclipse.jgit.attributes.FilterCommandRegistry;
26  import org.eclipse.jgit.junit.RepositoryTestCase;
27  import org.eclipse.jgit.lfs.internal.LfsConnectionFactory;
28  import org.eclipse.jgit.lfs.lib.Constants;
29  import org.eclipse.jgit.lib.ConfigConstants;
30  import org.eclipse.jgit.lib.Repository;
31  import org.eclipse.jgit.lib.StoredConfig;
32  import org.eclipse.jgit.transport.http.HttpConnection;
33  import org.eclipse.jgit.util.HttpSupport;
34  import org.junit.AfterClass;
35  import org.junit.Before;
36  import org.junit.BeforeClass;
37  import org.junit.Test;
38  
39  /**
40   * Test if the lfs config is used in the correct way during checkout.
41   *
42   * Two lfs-files are created, one that comes before .gitattributes and
43   * .lfsconfig in git order (".aaa.txt") and one that comes after ("zzz.txt").
44   *
45   * During checkout/reset it is tested if the correct version of the lfs config
46   * is used.
47   *
48   * TODO: The current behavior seems a little bit strange/unintuitive. Some files
49   * are checked out before and some after the config files. This leads to the
50   * behavior, that during a single command the config changes. Since this seems
51   * to be the same way in native git, the behavior is accepted for now.
52   *
53   */
54  public class LfsConfigGitTest extends RepositoryTestCase {
55  
56  	private static final String SMUDGE_NAME = org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
57  			+ Constants.ATTR_FILTER_DRIVER_PREFIX
58  			+ org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_SMUDGE;
59  
60  	private static final String LFS_SERVER_URI1 = "https://lfs.server1/test/uri";
61  
62  	private static final String EXPECTED_SERVER_URL1 = LFS_SERVER_URI1
63  			+ Protocol.OBJECTS_LFS_ENDPOINT;
64  
65  	private static final String LFS_SERVER_URI2 = "https://lfs.server2/test/uri";
66  
67  	private static final String EXPECTED_SERVER_URL2 = LFS_SERVER_URI2
68  			+ Protocol.OBJECTS_LFS_ENDPOINT;
69  
70  	private static final String LFS_SERVER_URI3 = "https://lfs.server3/test/uri";
71  
72  	private static final String EXPECTED_SERVER_URL3 = LFS_SERVER_URI3
73  			+ Protocol.OBJECTS_LFS_ENDPOINT;
74  
75  	private static final String FAKE_LFS_POINTER1 = "version https://git-lfs.github.com/spec/v1\n"
76  			+ "oid sha256:6ce9fab52ee9a6c4c097def4e049c6acdeba44c99d26e83ba80adec1473c9b2d\n"
77  			+ "size 253952\n";
78  
79  	private static final String FAKE_LFS_POINTER2 = "version https://git-lfs.github.com/spec/v1\n"
80  			+ "oid sha256:a4b711cd989863ae2038758a62672138347abbbae4076a7ad3a545fda7d08f82\n"
81  			+ "size 67072\n";
82  
83  	private static List<String> checkoutURLs = new ArrayList<>();
84  
85  	static class SmudgeFilterMock extends FilterCommand {
86  		public SmudgeFilterMock(Repository db, InputStream in,
87  				OutputStream out) throws IOException {
88  			super(in, out);
89  			HttpConnection lfsServerConn = LfsConnectionFactory.getLfsConnection(db,
90  					HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD);
91  			checkoutURLs.add(lfsServerConn.getURL().toString());
92  		}
93  
94  		@Override
95  		public int run() throws IOException {
96  			// Stupid no impl
97  			in.transferTo(out);
98  			return -1;
99  		}
100 	}
101 
102 	@BeforeClass
103 	public static void installLfs() {
104 		FilterCommandRegistry.register(SMUDGE_NAME, SmudgeFilterMock::new);
105 	}
106 
107 	@AfterClass
108 	public static void removeLfs() {
109 		FilterCommandRegistry.unregister(SMUDGE_NAME);
110 	}
111 
112 	private Git git;
113 
114 	@Override
115 	@Before
116 	public void setUp() throws Exception {
117 		super.setUp();
118 		git = new Git(db);
119 		// commit something
120 		writeTrashFile("Test.txt", "Hello world");
121 		git.add().addFilepattern("Test.txt").call();
122 		git.commit().setMessage("Initial commit").call();
123 		// prepare the config for LFS
124 		StoredConfig config = git.getRepository().getConfig();
125 		config.setString("filter", "lfs", "smudge", SMUDGE_NAME);
126 		config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
127 				ConfigConstants.CONFIG_KEY_AUTOCRLF, "false");
128 		config.save();
129 
130 		fileBefore = null;
131 		fileAfter = null;
132 		configFile = null;
133 		gitAttributesFile = null;
134 	}
135 
136 	File fileBefore;
137 
138 	File fileAfter;
139 
140 	File configFile;
141 
142 	File gitAttributesFile;
143 
144 	private void createLfsFiles(String lfsPointer) throws Exception {
145 		/*
146 		 * FileNames ".aaa.txt" and "zzz.txt" seem to be sufficient to get the
147 		 * desired checkout order before and after ".lfsconfig", at least in a
148 		 * number of manual tries. Since the files to checkout are contained in
149 		 * a set (see DirCacheCheckout::doCheckout) the order cannot be
150 		 * guaranteed.
151 		 */
152 
153 		//File to be checked out before lfs config
154 		String fileNameBefore = ".aaa.txt";
155 		fileBefore = writeTrashFile(fileNameBefore, lfsPointer);
156 		git.add().addFilepattern(fileNameBefore).call();
157 
158 		// File to be checked out after lfs config
159 		String fileNameAfter = "zzz.txt";
160 		fileAfter = writeTrashFile(fileNameAfter, lfsPointer);
161 		git.add().addFilepattern(fileNameAfter).call();
162 
163 		git.commit().setMessage("Commit LFS Pointer files").call();
164 	}
165 
166 
167 	private String addLfsConfigFiles(String lfsServerUrl) throws Exception {
168 		// Add config files to the repo
169 		String lfsConfig1 = createLfsConfig(lfsServerUrl);
170 		git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
171 		// Modify gitattributes on second call, to force checkout too.
172 		if (gitAttributesFile == null) {
173 			gitAttributesFile = writeTrashFile(".gitattributes",
174 				"*.txt filter=lfs");
175 		} else {
176 			gitAttributesFile = writeTrashFile(".gitattributes",
177 					"*.txt filter=lfs\n");
178 		}
179 
180 		git.add().addFilepattern(".gitattributes").call();
181 		git.commit().setMessage("Commit config files").call();
182 		return lfsConfig1;
183 	}
184 
185 	private String createLfsConfig(String lfsServerUrl) throws IOException {
186 		String lfsConfig1 = "[lfs]\n    url = " + lfsServerUrl;
187 		configFile = writeTrashFile(Constants.DOT_LFS_CONFIG, lfsConfig1);
188 		return lfsConfig1;
189 	}
190 
191 	@Test
192 	public void checkoutLfsObjects_reset() throws Exception {
193 		createLfsFiles(FAKE_LFS_POINTER1);
194 		String lfsConfig1 = addLfsConfigFiles(LFS_SERVER_URI1);
195 
196 		// Delete files to force action on reset
197 		assertTrue(configFile.delete());
198 		assertTrue(fileBefore.delete());
199 		assertTrue(fileAfter.delete());
200 
201 		assertTrue(gitAttributesFile.delete());
202 
203 		// create config file with different url
204 		createLfsConfig(LFS_SERVER_URI3);
205 
206 		checkoutURLs.clear();
207 		git.reset().setMode(ResetType.HARD).call();
208 
209 		checkFile(configFile, lfsConfig1);
210 		checkFile(fileBefore, FAKE_LFS_POINTER1);
211 		checkFile(fileAfter, FAKE_LFS_POINTER1);
212 
213 		assertEquals(2, checkoutURLs.size());
214 		// TODO: Should may be EXPECTED_SERVR_URL1
215 		assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(0));
216 		assertEquals(EXPECTED_SERVER_URL1, checkoutURLs.get(1));
217 	}
218 
219 	@Test
220 	public void checkoutLfsObjects_BranchSwitch() throws Exception {
221 		// Create a new branch "URL1" and add config files
222 		git.checkout().setCreateBranch(true).setName("URL1").call();
223 
224 		createLfsFiles(FAKE_LFS_POINTER1);
225 		String lfsConfig1 = addLfsConfigFiles(LFS_SERVER_URI1);
226 
227 		// Create a second new branch "URL2" and add config files
228 		git.checkout().setCreateBranch(true).setName("URL2").call();
229 
230 		createLfsFiles(FAKE_LFS_POINTER2);
231 		String lfsConfig2 = addLfsConfigFiles(LFS_SERVER_URI2);
232 
233 		checkFile(configFile, lfsConfig2);
234 		checkFile(fileBefore, FAKE_LFS_POINTER2);
235 		checkFile(fileAfter, FAKE_LFS_POINTER2);
236 
237 		checkoutURLs.clear();
238 		git.checkout().setName("URL1").call();
239 
240 		checkFile(configFile, lfsConfig1);
241 		checkFile(fileBefore, FAKE_LFS_POINTER1);
242 		checkFile(fileAfter, FAKE_LFS_POINTER1);
243 
244 		assertEquals(2, checkoutURLs.size());
245 		// TODO: Should may be EXPECTED_SERVR_URL1
246 		assertEquals(EXPECTED_SERVER_URL2, checkoutURLs.get(0));
247 		assertEquals(EXPECTED_SERVER_URL1, checkoutURLs.get(1));
248 
249 		checkoutURLs.clear();
250 		git.checkout().setName("URL2").call();
251 
252 		checkFile(configFile, lfsConfig2);
253 		checkFile(fileBefore, FAKE_LFS_POINTER2);
254 		checkFile(fileAfter, FAKE_LFS_POINTER2);
255 
256 		assertEquals(2, checkoutURLs.size());
257 		// TODO: Should may be EXPECTED_SERVR_URL2
258 		assertEquals(EXPECTED_SERVER_URL1, checkoutURLs.get(0));
259 		assertEquals(EXPECTED_SERVER_URL2, checkoutURLs.get(1));
260 	}
261 
262 	@Test
263 	public void checkoutLfsObjects_BranchSwitch_ModifiedLocal()
264 			throws Exception {
265 
266 		// Create a new branch "URL1" and add config files
267 		git.checkout().setCreateBranch(true).setName("URL1").call();
268 
269 		createLfsFiles(FAKE_LFS_POINTER1);
270 		addLfsConfigFiles(LFS_SERVER_URI1);
271 
272 		// Create a second new branch "URL2" and add config files
273 		git.checkout().setCreateBranch(true).setName("URL2").call();
274 
275 		createLfsFiles(FAKE_LFS_POINTER2);
276 		addLfsConfigFiles(LFS_SERVER_URI1);
277 
278 		// create config file with different url
279 		assertTrue(configFile.delete());
280 		String lfsConfig3 = createLfsConfig(LFS_SERVER_URI3);
281 
282 		checkFile(configFile, lfsConfig3);
283 		checkFile(fileBefore, FAKE_LFS_POINTER2);
284 		checkFile(fileAfter, FAKE_LFS_POINTER2);
285 
286 		checkoutURLs.clear();
287 		git.checkout().setName("URL1").call();
288 
289 		checkFile(fileBefore, FAKE_LFS_POINTER1);
290 		checkFile(fileAfter, FAKE_LFS_POINTER1);
291 		checkFile(configFile, lfsConfig3);
292 
293 		assertEquals(2, checkoutURLs.size());
294 
295 		assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(0));
296 		assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(1));
297 
298 		checkoutURLs.clear();
299 		git.checkout().setName("URL2").call();
300 
301 		checkFile(fileBefore, FAKE_LFS_POINTER2);
302 		checkFile(fileAfter, FAKE_LFS_POINTER2);
303 		checkFile(configFile, lfsConfig3);
304 
305 		assertEquals(2, checkoutURLs.size());
306 		assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(0));
307 		assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(1));
308 	}
309 }