View Javadoc
1   /*
2    * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com>
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.internal.storage.file;
45  
46  import static org.junit.Assert.assertEquals;
47  import static org.junit.Assert.assertFalse;
48  import static org.junit.Assert.assertTrue;
49  
50  import java.io.File;
51  import java.io.IOException;
52  import java.util.ArrayList;
53  import java.util.Collection;
54  import java.util.Date;
55  import java.util.List;
56  
57  import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
58  import org.eclipse.jgit.lib.ConfigConstants;
59  import org.eclipse.jgit.lib.RefUpdate;
60  import org.eclipse.jgit.revwalk.RevCommit;
61  import org.eclipse.jgit.storage.file.FileBasedConfig;
62  import org.eclipse.jgit.storage.pack.PackConfig;
63  import org.junit.Test;
64  import org.junit.experimental.theories.DataPoints;
65  import org.junit.experimental.theories.Theories;
66  import org.junit.experimental.theories.Theory;
67  import org.junit.runner.RunWith;
68  
69  @RunWith(Theories.class)
70  public class GcBasicPackingTest extends GcTestCase {
71  	@DataPoints
72  	public static boolean[] aggressiveValues = { true, false };
73  
74  	@Theory
75  	public void repackEmptyRepo_noPackCreated(boolean aggressive)
76  			throws IOException {
77  		configureGc(gc, aggressive);
78  		gc.repack();
79  		assertEquals(0, repo.getObjectDatabase().getPacks().size());
80  	}
81  
82  	@Theory
83  	public void testPackRepoWithNoRefs(boolean aggressive) throws Exception {
84  		tr.commit().add("A", "A").add("B", "B").create();
85  		stats = gc.getStatistics();
86  		assertEquals(4, stats.numberOfLooseObjects);
87  		assertEquals(0, stats.numberOfPackedObjects);
88  		configureGc(gc, aggressive);
89  		gc.gc();
90  		stats = gc.getStatistics();
91  		assertEquals(4, stats.numberOfLooseObjects);
92  		assertEquals(0, stats.numberOfPackedObjects);
93  		assertEquals(0, stats.numberOfPackFiles);
94  		assertEquals(0, stats.numberOfBitmaps);
95  	}
96  
97  	@Theory
98  	public void testPack2Commits(boolean aggressive) throws Exception {
99  		BranchBuilder bb = tr.branch("refs/heads/master");
100 		bb.commit().add("A", "A").add("B", "B").create();
101 		bb.commit().add("A", "A2").add("B", "B2").create();
102 
103 		stats = gc.getStatistics();
104 		assertEquals(8, stats.numberOfLooseObjects);
105 		assertEquals(0, stats.numberOfPackedObjects);
106 		configureGc(gc, aggressive);
107 		gc.gc();
108 		stats = gc.getStatistics();
109 		assertEquals(0, stats.numberOfLooseObjects);
110 		assertEquals(8, stats.numberOfPackedObjects);
111 		assertEquals(1, stats.numberOfPackFiles);
112 		assertEquals(2, stats.numberOfBitmaps);
113 	}
114 
115 	@Theory
116 	public void testPackAllObjectsInOnePack(boolean aggressive)
117 			throws Exception {
118 		tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
119 				.create();
120 		stats = gc.getStatistics();
121 		assertEquals(4, stats.numberOfLooseObjects);
122 		assertEquals(0, stats.numberOfPackedObjects);
123 		configureGc(gc, aggressive);
124 		gc.gc();
125 		stats = gc.getStatistics();
126 		assertEquals(0, stats.numberOfLooseObjects);
127 		assertEquals(4, stats.numberOfPackedObjects);
128 		assertEquals(1, stats.numberOfPackFiles);
129 		assertEquals(1, stats.numberOfBitmaps);
130 
131 		// Do the gc again and check that it hasn't changed anything
132 		gc.gc();
133 		stats = gc.getStatistics();
134 		assertEquals(0, stats.numberOfLooseObjects);
135 		assertEquals(4, stats.numberOfPackedObjects);
136 		assertEquals(1, stats.numberOfPackFiles);
137 		assertEquals(1, stats.numberOfBitmaps);
138 	}
139 
140 	@Theory
141 	public void testPackCommitsAndLooseOne(boolean aggressive)
142 			throws Exception {
143 		BranchBuilder bb = tr.branch("refs/heads/master");
144 		RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
145 		bb.commit().add("A", "A2").add("B", "B2").create();
146 		tr.update("refs/heads/master", first);
147 
148 		stats = gc.getStatistics();
149 		assertEquals(8, stats.numberOfLooseObjects);
150 		assertEquals(0, stats.numberOfPackedObjects);
151 		configureGc(gc, aggressive);
152 		gc.gc();
153 		stats = gc.getStatistics();
154 		assertEquals(0, stats.numberOfLooseObjects);
155 		assertEquals(8, stats.numberOfPackedObjects);
156 		assertEquals(2, stats.numberOfPackFiles);
157 		assertEquals(1, stats.numberOfBitmaps);
158 	}
159 
160 	@Theory
161 	public void testNotPackTwice(boolean aggressive) throws Exception {
162 		BranchBuilder bb = tr.branch("refs/heads/master");
163 		RevCommit first = bb.commit().message("M").add("M", "M").create();
164 		bb.commit().message("B").add("B", "Q").create();
165 		bb.commit().message("A").add("A", "A").create();
166 		RevCommit second = tr.commit().parent(first).message("R").add("R", "Q")
167 				.create();
168 		tr.update("refs/tags/t1", second);
169 
170 		Collection<PackFile> oldPacks = tr.getRepository().getObjectDatabase()
171 				.getPacks();
172 		assertEquals(0, oldPacks.size());
173 		stats = gc.getStatistics();
174 		assertEquals(11, stats.numberOfLooseObjects);
175 		assertEquals(0, stats.numberOfPackedObjects);
176 
177 		gc.setExpireAgeMillis(0);
178 		fsTick();
179 		configureGc(gc, aggressive);
180 		gc.gc();
181 		stats = gc.getStatistics();
182 		assertEquals(0, stats.numberOfLooseObjects);
183 
184 		List<PackFile> packs = new ArrayList<>(
185 				repo.getObjectDatabase().getPacks());
186 		assertEquals(11, packs.get(0).getObjectCount());
187 	}
188 
189 	@Test
190 	public void testDonePruneTooYoungPacks() throws Exception {
191 		BranchBuilder bb = tr.branch("refs/heads/master");
192 		bb.commit().message("M").add("M", "M").create();
193 
194 		String tempRef = "refs/heads/soon-to-be-unreferenced";
195 		BranchBuilder bb2 = tr.branch(tempRef);
196 		bb2.commit().message("M").add("M", "M").create();
197 
198 		gc.setExpireAgeMillis(0);
199 		gc.gc();
200 		stats = gc.getStatistics();
201 		assertEquals(0, stats.numberOfLooseObjects);
202 		assertEquals(4, stats.numberOfPackedObjects);
203 		assertEquals(1, stats.numberOfPackFiles);
204 		File oldPackfile = tr.getRepository().getObjectDatabase().getPacks()
205 				.iterator().next().getPackFile();
206 
207 		fsTick();
208 
209 		// delete the temp ref, orphaning its commit
210 		RefUpdate update = tr.getRepository().getRefDatabase().newUpdate(tempRef, false);
211 		update.setForceUpdate(true);
212 		update.delete();
213 
214 		bb.commit().message("B").add("B", "Q").create();
215 
216 		// The old packfile is too young to be deleted. We should end up with
217 		// two pack files
218 		gc.setExpire(new Date(oldPackfile.lastModified() - 1));
219 		gc.gc();
220 		stats = gc.getStatistics();
221 		assertEquals(0, stats.numberOfLooseObjects);
222 		// if objects exist in multiple packFiles then they are counted multiple
223 		// times
224 		assertEquals(10, stats.numberOfPackedObjects);
225 		assertEquals(2, stats.numberOfPackFiles);
226 
227 		// repack again but now without a grace period for loose objects. Since
228 		// we don't have loose objects anymore this shouldn't change anything
229 		gc.setExpireAgeMillis(0);
230 		gc.gc();
231 		stats = gc.getStatistics();
232 		assertEquals(0, stats.numberOfLooseObjects);
233 		// if objects exist in multiple packFiles then they are counted multiple
234 		// times
235 		assertEquals(10, stats.numberOfPackedObjects);
236 		assertEquals(2, stats.numberOfPackFiles);
237 
238 		// repack again but now without a grace period for packfiles. We should
239 		// end up with one packfile
240 		gc.setPackExpireAgeMillis(0);
241 
242 		// we want to keep newly-loosened objects though
243 		gc.setExpireAgeMillis(-1);
244 
245 		gc.gc();
246 		stats = gc.getStatistics();
247 		assertEquals(1, stats.numberOfLooseObjects);
248 		// if objects exist in multiple packFiles then they are counted multiple
249 		// times
250 		assertEquals(6, stats.numberOfPackedObjects);
251 		assertEquals(1, stats.numberOfPackFiles);
252 	}
253 
254 	@Test
255 	public void testImmediatePruning() throws Exception {
256 		BranchBuilder bb = tr.branch("refs/heads/master");
257 		bb.commit().message("M").add("M", "M").create();
258 
259 		String tempRef = "refs/heads/soon-to-be-unreferenced";
260 		BranchBuilder bb2 = tr.branch(tempRef);
261 		bb2.commit().message("M").add("M", "M").create();
262 
263 		gc.setExpireAgeMillis(0);
264 		gc.gc();
265 		stats = gc.getStatistics();
266 
267 		fsTick();
268 
269 		// delete the temp ref, orphaning its commit
270 		RefUpdate update = tr.getRepository().getRefDatabase().newUpdate(tempRef, false);
271 		update.setForceUpdate(true);
272 		update.delete();
273 
274 		bb.commit().message("B").add("B", "Q").create();
275 
276 		// We want to immediately prune deleted objects
277 		FileBasedConfig config = repo.getConfig();
278 		config.setString(ConfigConstants.CONFIG_GC_SECTION, null,
279 			ConfigConstants.CONFIG_KEY_PRUNEEXPIRE, "now");
280 		config.save();
281 
282 		//And we don't want to keep packs full of dead objects
283 		gc.setPackExpireAgeMillis(0);
284 
285 		gc.gc();
286 		stats = gc.getStatistics();
287 		assertEquals(0, stats.numberOfLooseObjects);
288 		assertEquals(6, stats.numberOfPackedObjects);
289 		assertEquals(1, stats.numberOfPackFiles);
290 	}
291 
292 	@Test
293 	public void testPreserveAndPruneOldPacks() throws Exception {
294 		testPreserveOldPacks();
295 		configureGc(gc, false).setPrunePreserved(true);
296 		gc.gc();
297 
298 		assertFalse(repo.getObjectDatabase().getPreservedDirectory().exists());
299 	}
300 
301 	private void testPreserveOldPacks() throws Exception {
302 		BranchBuilder bb = tr.branch("refs/heads/master");
303 		bb.commit().message("P").add("P", "P").create();
304 
305 		// pack loose object into packfile
306 		gc.setExpireAgeMillis(0);
307 		gc.gc();
308 		File oldPackfile = tr.getRepository().getObjectDatabase().getPacks()
309 				.iterator().next().getPackFile();
310 		assertTrue(oldPackfile.exists());
311 
312 		fsTick();
313 		bb.commit().message("B").add("B", "Q").create();
314 
315 		// repack again but now without a grace period for packfiles. We should
316 		// end up with a new packfile and the old one should be placed in the
317 		// preserved directory
318 		gc.setPackExpireAgeMillis(0);
319 		configureGc(gc, false).setPreserveOldPacks(true);
320 		gc.gc();
321 
322 		File oldPackDir = repo.getObjectDatabase().getPreservedDirectory();
323 		String oldPackFileName = oldPackfile.getName();
324 		String oldPackName = oldPackFileName.substring(0,
325 				oldPackFileName.lastIndexOf('.')) + ".old-pack";  //$NON-NLS-1$
326 		File preservePackFile = new File(oldPackDir, oldPackName);
327 		assertTrue(preservePackFile.exists());
328 	}
329 
330 	private PackConfig configureGc(GC myGc, boolean aggressive) {
331 		PackConfig pconfig = new PackConfig(repo);
332 		if (aggressive) {
333 			pconfig.setDeltaSearchWindowSize(250);
334 			pconfig.setMaxDeltaDepth(250);
335 			pconfig.setReuseObjects(false);
336 		} else
337 			pconfig = new PackConfig(repo);
338 		myGc.setPackConfig(pconfig);
339 		return pconfig;
340 	}
341 }