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.hamcrest.Matchers.lessThanOrEqualTo;
47  import static org.junit.Assert.assertEquals;
48  import static org.junit.Assert.assertFalse;
49  import static org.junit.Assert.assertNotNull;
50  import static org.junit.Assert.assertNull;
51  import static org.junit.Assert.assertSame;
52  import static org.junit.Assert.assertThat;
53  
54  import java.io.File;
55  import java.io.IOException;
56  import java.nio.file.Files;
57  import java.nio.file.Path;
58  import java.util.concurrent.BrokenBarrierException;
59  import java.util.concurrent.Callable;
60  import java.util.concurrent.CyclicBarrier;
61  import java.util.concurrent.ExecutorService;
62  import java.util.concurrent.Executors;
63  import java.util.concurrent.Future;
64  import java.util.concurrent.TimeUnit;
65  
66  import org.eclipse.jgit.api.Git;
67  import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
68  import org.eclipse.jgit.lib.ConfigConstants;
69  import org.eclipse.jgit.lib.Constants;
70  import org.eclipse.jgit.lib.Ref.Storage;
71  import org.eclipse.jgit.lib.RefUpdate;
72  import org.eclipse.jgit.lib.RefUpdate.Result;
73  import org.eclipse.jgit.revwalk.RevBlob;
74  import org.eclipse.jgit.revwalk.RevCommit;
75  import org.eclipse.jgit.storage.file.FileBasedConfig;
76  import org.junit.Test;
77  
78  @SuppressWarnings("boxing")
79  public class GcPackRefsTest extends GcTestCase {
80  	@Test
81  	public void looseRefPacked() throws Exception {
82  		RevBlob a = tr.blob("a");
83  		tr.lightweightTag("t", a);
84  
85  		gc.packRefs();
86  		assertSame(repo.exactRef("refs/tags/t").getStorage(), Storage.PACKED);
87  	}
88  
89  	@Test
90  	public void emptyRefDirectoryDeleted() throws Exception {
91  		String ref = "dir/ref";
92  		tr.branch(ref).commit().create();
93  		String name = repo.findRef(ref).getName();
94  		Path dir = repo.getDirectory().toPath().resolve(name).getParent();
95  		assertNotNull(dir);
96  		gc.packRefs();
97  		assertFalse(Files.exists(dir));
98  	}
99  
100 	@Test
101 	public void concurrentOnlyOneWritesPackedRefs() throws Exception {
102 		RevBlob a = tr.blob("a");
103 		tr.lightweightTag("t", a);
104 
105 		CyclicBarrier syncPoint = new CyclicBarrier(2);
106 
107 		// Returns 0 for success, 1 in case of error when writing pack.
108 		Callable<Integer> packRefs = () -> {
109 			syncPoint.await();
110 			try {
111 				gc.packRefs();
112 				return 0;
113 			} catch (IOException e) {
114 				return 1;
115 			}
116 		};
117 		ExecutorService pool = Executors.newFixedThreadPool(2);
118 		try {
119 			Future<Integer> p1 = pool.submit(packRefs);
120 			Future<Integer> p2 = pool.submit(packRefs);
121 			assertThat(p1.get() + p2.get(), lessThanOrEqualTo(1));
122 		} finally {
123 			pool.shutdown();
124 			pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
125 		}
126 	}
127 
128 	@Test
129 	public void whileRefLockedRefNotPackedNoError()
130 			throws Exception {
131 		RevBlob a = tr.blob("a");
132 		tr.lightweightTag("t1", a);
133 		tr.lightweightTag("t2", a);
134 		LockFile refLock = new LockFile(new File(repo.getDirectory(),
135 				"refs/tags/t1"));
136 		try {
137 			refLock.lock();
138 			gc.packRefs();
139 		} finally {
140 			refLock.unlock();
141 		}
142 
143 		assertSame(repo.exactRef("refs/tags/t1").getStorage(), Storage.LOOSE);
144 		assertSame(repo.exactRef("refs/tags/t2").getStorage(), Storage.PACKED);
145 	}
146 
147 	@Test
148 	public void whileRefUpdatedRefUpdateSucceeds()
149 			throws Exception {
150 		RevBlob a = tr.blob("a");
151 		tr.lightweightTag("t", a);
152 		final RevBlob b = tr.blob("b");
153 
154 		final CyclicBarrier refUpdateLockedRef = new CyclicBarrier(2);
155 		final CyclicBarrier packRefsDone = new CyclicBarrier(2);
156 		ExecutorService pool = Executors.newFixedThreadPool(2);
157 		try {
158 			Future<Result> result = pool.submit(new Callable<Result>() {
159 
160 				@Override
161 				public Result call() throws Exception {
162 					RefUpdate update = new RefDirectoryUpdate(
163 							(RefDirectory) repo.getRefDatabase(),
164 							repo.exactRef("refs/tags/t")) {
165 						@Override
166 						public boolean isForceUpdate() {
167 							try {
168 								refUpdateLockedRef.await();
169 								packRefsDone.await();
170 							} catch (InterruptedException e) {
171 								Thread.currentThread().interrupt();
172 							} catch (BrokenBarrierException e) {
173 								Thread.currentThread().interrupt();
174 							}
175 							return super.isForceUpdate();
176 						}
177 					};
178 					update.setForceUpdate(true);
179 					update.setNewObjectId(b);
180 					return update.update();
181 				}
182 			});
183 
184 			pool.submit(new Callable<Void>() {
185 				@Override
186 				public Void call() throws Exception {
187 					refUpdateLockedRef.await();
188 					gc.packRefs();
189 					packRefsDone.await();
190 					return null;
191 				}
192 			});
193 
194 			assertSame(result.get(), Result.FORCED);
195 
196 		} finally {
197 			pool.shutdownNow();
198 			pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
199 		}
200 
201 		assertEquals(repo.exactRef("refs/tags/t").getObjectId(), b);
202 	}
203 
204 	@Test
205 	public void dontPackHEAD_nonBare() throws Exception {
206 		BranchBuilder bb = tr.branch("refs/heads/side");
207 		RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
208 		bb.commit().add("A", "A2").add("B", "B2").create();
209 		Git git = Git.wrap(repo);
210 
211 		// check for the unborn branch master. HEAD should point to master and
212 		// master doesn't exist.
213 		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
214 				"refs/heads/master");
215 		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
216 		gc.packRefs();
217 		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
218 		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
219 				"refs/heads/master");
220 		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
221 
222 		git.checkout().setName("refs/heads/side").call();
223 		gc.packRefs();
224 		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
225 
226 		// check for detached HEAD
227 		git.checkout().setName(first.getName()).call();
228 		gc.packRefs();
229 		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
230 	}
231 
232 	@Test
233 	public void dontPackHEAD_bare() throws Exception {
234 		BranchBuilder bb = tr.branch("refs/heads/side");
235 		bb.commit().add("A", "A").add("B", "B").create();
236 		RevCommit second = bb.commit().add("A", "A2").add("B", "B2").create();
237 
238 		// Convert the repo to be bare
239 		FileBasedConfig cfg = repo.getConfig();
240 		cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
241 				ConfigConstants.CONFIG_KEY_BARE, true);
242 		cfg.save();
243 		Git git = Git.open(repo.getDirectory());
244 		repo = (FileRepository) git.getRepository();
245 
246 		// check for the unborn branch master. HEAD should point to master and
247 		// master doesn't exist.
248 		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
249 				"refs/heads/master");
250 		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
251 		gc.packRefs();
252 		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
253 		assertEquals(repo.exactRef("HEAD").getTarget().getName(),
254 				"refs/heads/master");
255 		assertNull(repo.exactRef("HEAD").getTarget().getObjectId());
256 
257 		// check for non-detached HEAD
258 		repo.updateRef(Constants.HEAD).link("refs/heads/side");
259 		gc.packRefs();
260 		assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE);
261 		assertEquals(repo.exactRef("HEAD").getTarget().getObjectId(),
262 				second.getId());
263 	}
264 }