View Javadoc
1   /*
2    * Copyright (C) 2008-2009, Google Inc.
3    * Copyright (C) 2008, Mike Ralphson <mike@abacus.co.uk>
4    * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
5    * and other copyright owners as documented in the project's IP log.
6    *
7    * This program and the accompanying materials are made available
8    * under the terms of the Eclipse Distribution License v1.0 which
9    * accompanies this distribution, is reproduced below, and is
10   * available at http://www.eclipse.org/org/documents/edl-v10.php
11   *
12   * All rights reserved.
13   *
14   * Redistribution and use in source and binary forms, with or
15   * without modification, are permitted provided that the following
16   * conditions are met:
17   *
18   * - Redistributions of source code must retain the above copyright
19   *   notice, this list of conditions and the following disclaimer.
20   *
21   * - Redistributions in binary form must reproduce the above
22   *   copyright notice, this list of conditions and the following
23   *   disclaimer in the documentation and/or other materials provided
24   *   with the distribution.
25   *
26   * - Neither the name of the Eclipse Foundation, Inc. nor the
27   *   names of its contributors may be used to endorse or promote
28   *   products derived from this software without specific prior
29   *   written permission.
30   *
31   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
32   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
33   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
36   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
38   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
40   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
43   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44   */
45  
46  package org.eclipse.jgit.transport;
47  
48  import static java.nio.charset.StandardCharsets.UTF_8;
49  import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
50  import static org.junit.Assert.assertEquals;
51  import static org.junit.Assert.assertNotNull;
52  import static org.junit.Assert.assertNull;
53  import static org.junit.Assert.assertTrue;
54  import static org.junit.Assert.fail;
55  
56  import java.io.ByteArrayInputStream;
57  import java.io.ByteArrayOutputStream;
58  import java.io.FileNotFoundException;
59  import java.io.IOException;
60  import java.net.URISyntaxException;
61  import java.util.Collections;
62  import java.util.Set;
63  
64  import org.eclipse.jgit.errors.MissingBundlePrerequisiteException;
65  import org.eclipse.jgit.errors.MissingObjectException;
66  import org.eclipse.jgit.errors.NotSupportedException;
67  import org.eclipse.jgit.errors.TransportException;
68  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
69  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
70  import org.eclipse.jgit.lib.Constants;
71  import org.eclipse.jgit.lib.NullProgressMonitor;
72  import org.eclipse.jgit.lib.ObjectId;
73  import org.eclipse.jgit.lib.ObjectInserter;
74  import org.eclipse.jgit.lib.ObjectReader;
75  import org.eclipse.jgit.lib.Ref;
76  import org.eclipse.jgit.lib.Repository;
77  import org.eclipse.jgit.revwalk.RevCommit;
78  import org.eclipse.jgit.revwalk.RevWalk;
79  import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
80  import org.junit.Test;
81  
82  public class BundleWriterTest extends SampleDataRepositoryTestCase {
83  
84  	@Test
85  	public void testWriteSingleRef() throws Exception {
86  		// Create a tiny bundle, (well one of) the first commits only
87  		final byte[] bundle = makeBundle("refs/heads/firstcommit",
88  				"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", null);
89  
90  		// Then we clone a new repo from that bundle and do a simple test. This
91  		// makes sure we could read the bundle we created.
92  		Repository newRepo = createBareRepository();
93  		FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
94  		Ref advertisedRef = fetchResult
95  				.getAdvertisedRef("refs/heads/firstcommit");
96  
97  		// We expect first commit to appear by id
98  		assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", advertisedRef
99  				.getObjectId().name());
100 		// ..and by name as the bundle created a new ref
101 		assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", newRepo
102 				.resolve("refs/heads/firstcommit").name());
103 	}
104 
105 	@Test
106 	public void testWriteHEAD() throws Exception {
107 		byte[] bundle = makeBundle("HEAD",
108 				"42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", null);
109 
110 		Repository newRepo = createBareRepository();
111 		FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
112 		Ref advertisedRef = fetchResult.getAdvertisedRef("HEAD");
113 
114 		assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", advertisedRef
115 				.getObjectId().name());
116 	}
117 
118 	@Test
119 	public void testIncrementalBundle() throws Exception {
120 		byte[] bundle;
121 
122 		// Create a small bundle, an early commit
123 		bundle = makeBundle("refs/heads/aa", db.resolve("a").name(), null);
124 
125 		// Then we clone a new repo from that bundle and do a simple test. This
126 		// makes sure
127 		// we could read the bundle we created.
128 		Repository newRepo = createBareRepository();
129 		FetchResult fetchResult = fetchFromBundle(newRepo, bundle);
130 		Ref advertisedRef = fetchResult.getAdvertisedRef("refs/heads/aa");
131 
132 		assertEquals(db.resolve("a").name(), advertisedRef.getObjectId().name());
133 		assertEquals(db.resolve("a").name(), newRepo.resolve("refs/heads/aa")
134 				.name());
135 		assertNull(newRepo.resolve("refs/heads/a"));
136 
137 		// Next an incremental bundle
138 		try (RevWalk rw = new RevWalk(db)) {
139 			bundle = makeBundle("refs/heads/cc", db.resolve("c").name(),
140 					rw.parseCommit(db.resolve("a").toObjectId()));
141 			fetchResult = fetchFromBundle(newRepo, bundle);
142 			advertisedRef = fetchResult.getAdvertisedRef("refs/heads/cc");
143 			assertEquals(db.resolve("c").name(), advertisedRef.getObjectId().name());
144 			assertEquals(db.resolve("c").name(), newRepo.resolve("refs/heads/cc")
145 					.name());
146 			assertNull(newRepo.resolve("refs/heads/c"));
147 			assertNull(newRepo.resolve("refs/heads/a")); // still unknown
148 
149 			try {
150 				// Check that we actually needed the first bundle
151 				Repository newRepo2 = createBareRepository();
152 				fetchResult = fetchFromBundle(newRepo2, bundle);
153 				fail("We should not be able to fetch from bundle with prerequisites that are not fulfilled");
154 			} catch (MissingBundlePrerequisiteException e) {
155 				assertTrue(e.getMessage()
156 						.indexOf(db.resolve("refs/heads/a").name()) >= 0);
157 			}
158 		}
159 	}
160 
161 	@Test
162 	public void testAbortWrite() throws Exception {
163 		boolean caught = false;
164 		try {
165 			makeBundleWithCallback(
166 					"refs/heads/aa", db.resolve("a").name(), null, false);
167 		} catch (WriteAbortedException e) {
168 			caught = true;
169 		}
170 		assertTrue(caught);
171 	}
172 
173 	@Test
174 	public void testCustomObjectReader() throws Exception {
175 		String refName = "refs/heads/blob";
176 		String data = "unflushed data";
177 		ObjectId id;
178 		ByteArrayOutputStream out = new ByteArrayOutputStream();
179 		try (Repository repo = new InMemoryRepository(
180 					new DfsRepositoryDescription("repo"));
181 				ObjectInserter ins = repo.newObjectInserter();
182 				ObjectReader or = ins.newReader()) {
183 			id = ins.insert(OBJ_BLOB, Constants.encode(data));
184 			BundleWriter bw = new BundleWriter(or);
185 			bw.include(refName, id);
186 			bw.writeBundle(NullProgressMonitor.INSTANCE, out);
187 			assertNull(repo.exactRef(refName));
188 			try {
189 				repo.open(id, OBJ_BLOB);
190 				fail("We should not be able to open the unflushed blob");
191 			} catch (MissingObjectException e) {
192 				// Expected.
193 			}
194 		}
195 
196 		try (Repository repo = new InMemoryRepository(
197 					new DfsRepositoryDescription("copy"))) {
198 			fetchFromBundle(repo, out.toByteArray());
199 			Ref ref = repo.exactRef(refName);
200 			assertNotNull(ref);
201 			assertEquals(id, ref.getObjectId());
202 			assertEquals(data, new String(repo.open(id, OBJ_BLOB).getBytes(), UTF_8));
203 		}
204 	}
205 
206 	private static FetchResult fetchFromBundle(final Repository newRepo,
207 			final byte[] bundle) throws URISyntaxException,
208 			NotSupportedException, TransportException {
209 		final URIish uri = new URIish("in-memory://");
210 		final ByteArrayInputStream in = new ByteArrayInputStream(bundle);
211 		final RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*");
212 		final Set<RefSpec> refs = Collections.singleton(rs);
213 		try (TransportBundleStream transport = new TransportBundleStream(
214 				newRepo, uri, in)) {
215 			return transport.fetch(NullProgressMonitor.INSTANCE, refs);
216 		}
217 	}
218 
219 	private byte[] makeBundle(final String name,
220 			final String anObjectToInclude, final RevCommit assume)
221 			throws FileNotFoundException, IOException {
222 		return makeBundleWithCallback(name, anObjectToInclude, assume, true);
223 	}
224 
225 	private byte[] makeBundleWithCallback(final String name,
226 			final String anObjectToInclude, final RevCommit assume,
227 			boolean value)
228 			throws FileNotFoundException, IOException {
229 		final BundleWriter bw;
230 
231 		bw = new BundleWriter(db);
232 		bw.setObjectCountCallback(new NaiveObjectCountCallback(value));
233 		bw.include(name, ObjectId.fromString(anObjectToInclude));
234 		if (assume != null)
235 			bw.assume(assume);
236 		final ByteArrayOutputStream out = new ByteArrayOutputStream();
237 		bw.writeBundle(NullProgressMonitor.INSTANCE, out);
238 		return out.toByteArray();
239 	}
240 
241 	private static class NaiveObjectCountCallback
242 			implements ObjectCountCallback {
243 		private final boolean value;
244 
245 		NaiveObjectCountCallback(boolean value) {
246 			this.value = value;
247 		}
248 
249 		@Override
250 		public void setObjectCount(long unused) throws WriteAbortedException {
251 			if (!value)
252 				throw new WriteAbortedException();
253 		}
254 	}
255 
256 }