View Javadoc
1   /*
2    * Copyright (C) 2016, Google Inc.
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.reftree;
45  
46  import static org.eclipse.jgit.lib.Constants.HEAD;
47  import static org.eclipse.jgit.lib.Constants.R_HEADS;
48  import static org.eclipse.jgit.lib.Constants.R_TAGS;
49  import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
50  import static org.eclipse.jgit.lib.Ref.Storage.NEW;
51  import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
52  import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
53  import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
54  import static org.junit.Assert.assertEquals;
55  import static org.junit.Assert.assertFalse;
56  import static org.junit.Assert.assertNotNull;
57  import static org.junit.Assert.assertNull;
58  import static org.junit.Assert.assertSame;
59  import static org.junit.Assert.assertTrue;
60  
61  import java.io.IOException;
62  import java.util.Arrays;
63  import java.util.Collections;
64  
65  import org.eclipse.jgit.errors.MissingObjectException;
66  import org.eclipse.jgit.internal.JGitText;
67  import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
68  import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
69  import org.eclipse.jgit.junit.TestRepository;
70  import org.eclipse.jgit.lib.ObjectId;
71  import org.eclipse.jgit.lib.ObjectIdRef;
72  import org.eclipse.jgit.lib.ObjectInserter;
73  import org.eclipse.jgit.lib.ObjectReader;
74  import org.eclipse.jgit.lib.Ref;
75  import org.eclipse.jgit.lib.SymbolicRef;
76  import org.eclipse.jgit.revwalk.RevBlob;
77  import org.eclipse.jgit.revwalk.RevTag;
78  import org.eclipse.jgit.revwalk.RevWalk;
79  import org.eclipse.jgit.transport.ReceiveCommand;
80  import org.junit.Before;
81  import org.junit.Test;
82  
83  public class RefTreeTest {
84  	private static final String R_MASTER = R_HEADS + "master";
85  	private InMemoryRepository repo;
86  	private TestRepository<InMemoryRepository> git;
87  
88  	@Before
89  	public void setUp() throws IOException {
90  		repo = new InMemoryRepository(new DfsRepositoryDescription("RefTree"));
91  		git = new TestRepository<>(repo);
92  	}
93  
94  	@Test
95  	public void testEmptyTree() throws IOException {
96  		RefTree tree = RefTree.newEmptyTree();
97  		try (ObjectReader reader = repo.newObjectReader()) {
98  			assertNull(HEAD, tree.exactRef(reader, HEAD));
99  			assertNull("master", tree.exactRef(reader, R_MASTER));
100 		}
101 	}
102 
103 	@Test
104 	public void testApplyThenReadMaster() throws Exception {
105 		RefTree tree = RefTree.newEmptyTree();
106 		RevBlob id = git.blob("A");
107 		Command cmd = new Command(null, ref(R_MASTER, id));
108 		assertTrue(tree.apply(Collections.singletonList(cmd)));
109 		assertSame(NOT_ATTEMPTED, cmd.getResult());
110 
111 		try (ObjectReader reader = repo.newObjectReader()) {
112 			Ref m = tree.exactRef(reader, R_MASTER);
113 			assertNotNull(R_MASTER, m);
114 			assertEquals(R_MASTER, m.getName());
115 			assertEquals(id, m.getObjectId());
116 			assertTrue("peeled", m.isPeeled());
117 		}
118 	}
119 
120 	@Test
121 	public void testUpdateMaster() throws Exception {
122 		RefTree tree = RefTree.newEmptyTree();
123 		RevBlob id1 = git.blob("A");
124 		Command cmd1 = new Command(null, ref(R_MASTER, id1));
125 		assertTrue(tree.apply(Collections.singletonList(cmd1)));
126 		assertSame(NOT_ATTEMPTED, cmd1.getResult());
127 
128 		RevBlob id2 = git.blob("B");
129 		Command cmd2 = new Command(ref(R_MASTER, id1), ref(R_MASTER, id2));
130 		assertTrue(tree.apply(Collections.singletonList(cmd2)));
131 		assertSame(NOT_ATTEMPTED, cmd2.getResult());
132 
133 		try (ObjectReader reader = repo.newObjectReader()) {
134 			Ref m = tree.exactRef(reader, R_MASTER);
135 			assertNotNull(R_MASTER, m);
136 			assertEquals(R_MASTER, m.getName());
137 			assertEquals(id2, m.getObjectId());
138 			assertTrue("peeled", m.isPeeled());
139 		}
140 	}
141 
142 	@Test
143 	public void testHeadSymref() throws Exception {
144 		RefTree tree = RefTree.newEmptyTree();
145 		RevBlob id = git.blob("A");
146 		Command cmd1 = new Command(null, ref(R_MASTER, id));
147 		Command cmd2 = new Command(null, symref(HEAD, R_MASTER));
148 		assertTrue(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 })));
149 		assertSame(NOT_ATTEMPTED, cmd1.getResult());
150 		assertSame(NOT_ATTEMPTED, cmd2.getResult());
151 
152 		try (ObjectReader reader = repo.newObjectReader()) {
153 			Ref m = tree.exactRef(reader, HEAD);
154 			assertNotNull(HEAD, m);
155 			assertEquals(HEAD, m.getName());
156 			assertTrue("symbolic", m.isSymbolic());
157 			assertNotNull(m.getTarget());
158 			assertEquals(R_MASTER, m.getTarget().getName());
159 			assertEquals(id, m.getTarget().getObjectId());
160 		}
161 
162 		// Writing flushes some buffers, re-read from blob.
163 		ObjectId newId = write(tree);
164 		try (ObjectReader reader = repo.newObjectReader();
165 				RevWalk rw = new RevWalk(reader)) {
166 			tree = RefTree.read(reader, rw.parseTree(newId));
167 			Ref m = tree.exactRef(reader, HEAD);
168 			assertEquals(R_MASTER, m.getTarget().getName());
169 		}
170 	}
171 
172 	@Test
173 	public void testTagIsPeeled() throws Exception {
174 		String name = "v1.0";
175 		RefTree tree = RefTree.newEmptyTree();
176 		RevBlob id = git.blob("A");
177 		RevTag tag = git.tag(name, id);
178 
179 		String ref = R_TAGS + name;
180 		Command cmd = create(ref, tag);
181 		assertTrue(tree.apply(Collections.singletonList(cmd)));
182 		assertSame(NOT_ATTEMPTED, cmd.getResult());
183 
184 		try (ObjectReader reader = repo.newObjectReader()) {
185 			Ref m = tree.exactRef(reader, ref);
186 			assertNotNull(ref, m);
187 			assertEquals(ref, m.getName());
188 			assertEquals(tag, m.getObjectId());
189 			assertTrue("peeled", m.isPeeled());
190 			assertEquals(id, m.getPeeledObjectId());
191 		}
192 	}
193 
194 	@Test
195 	public void testApplyAlreadyExists() throws Exception {
196 		RefTree tree = RefTree.newEmptyTree();
197 		RevBlob a = git.blob("A");
198 		Command cmd = new Command(null, ref(R_MASTER, a));
199 		assertTrue(tree.apply(Collections.singletonList(cmd)));
200 		ObjectId treeId = write(tree);
201 
202 		RevBlob b = git.blob("B");
203 		Command cmd1 = create(R_MASTER, b);
204 		Command cmd2 = create(R_MASTER, b);
205 		assertFalse(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 })));
206 		assertSame(LOCK_FAILURE, cmd1.getResult());
207 		assertSame(REJECTED_OTHER_REASON, cmd2.getResult());
208 		assertEquals(JGitText.get().transactionAborted, cmd2.getMessage());
209 		assertEquals(treeId, write(tree));
210 	}
211 
212 	@Test
213 	public void testApplyWrongOldId() throws Exception {
214 		RefTree tree = RefTree.newEmptyTree();
215 		RevBlob a = git.blob("A");
216 		Command cmd = new Command(null, ref(R_MASTER, a));
217 		assertTrue(tree.apply(Collections.singletonList(cmd)));
218 		ObjectId treeId = write(tree);
219 
220 		RevBlob b = git.blob("B");
221 		RevBlob c = git.blob("C");
222 		Command cmd1 = update(R_MASTER, b, c);
223 		Command cmd2 = create(R_MASTER, b);
224 		assertFalse(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 })));
225 		assertSame(LOCK_FAILURE, cmd1.getResult());
226 		assertSame(REJECTED_OTHER_REASON, cmd2.getResult());
227 		assertEquals(JGitText.get().transactionAborted, cmd2.getMessage());
228 		assertEquals(treeId, write(tree));
229 	}
230 
231 	@Test
232 	public void testApplyWrongOldIdButAlreadyCurrentIsNoOp() throws Exception {
233 		RefTree tree = RefTree.newEmptyTree();
234 		RevBlob a = git.blob("A");
235 		Command cmd = new Command(null, ref(R_MASTER, a));
236 		assertTrue(tree.apply(Collections.singletonList(cmd)));
237 		ObjectId treeId = write(tree);
238 
239 		RevBlob b = git.blob("B");
240 		cmd = update(R_MASTER, b, a);
241 		assertTrue(tree.apply(Collections.singletonList(cmd)));
242 		assertEquals(treeId, write(tree));
243 	}
244 
245 	@Test
246 	public void testApplyCannotCreateSubdirectory() throws Exception {
247 		RefTree tree = RefTree.newEmptyTree();
248 		RevBlob a = git.blob("A");
249 		Command cmd = new Command(null, ref(R_MASTER, a));
250 		assertTrue(tree.apply(Collections.singletonList(cmd)));
251 		ObjectId treeId = write(tree);
252 
253 		RevBlob b = git.blob("B");
254 		Command cmd1 = create(R_MASTER + "/fail", b);
255 		assertFalse(tree.apply(Collections.singletonList(cmd1)));
256 		assertSame(LOCK_FAILURE, cmd1.getResult());
257 		assertEquals(treeId, write(tree));
258 	}
259 
260 	@Test
261 	public void testApplyCannotCreateParentRef() throws Exception {
262 		RefTree tree = RefTree.newEmptyTree();
263 		RevBlob a = git.blob("A");
264 		Command cmd = new Command(null, ref(R_MASTER, a));
265 		assertTrue(tree.apply(Collections.singletonList(cmd)));
266 		ObjectId treeId = write(tree);
267 
268 		RevBlob b = git.blob("B");
269 		Command cmd1 = create("refs/heads", b);
270 		assertFalse(tree.apply(Collections.singletonList(cmd1)));
271 		assertSame(LOCK_FAILURE, cmd1.getResult());
272 		assertEquals(treeId, write(tree));
273 	}
274 
275 	private static Ref ref(String name, ObjectId id) {
276 		return new ObjectIdRef.PeeledNonTag(LOOSE, name, id);
277 	}
278 
279 	private static Ref symref(String name, String dest) {
280 		Ref d = new ObjectIdRef.PeeledNonTag(NEW, dest, null);
281 		return new SymbolicRef(name, d);
282 	}
283 
284 	private Command create(String name, ObjectId id)
285 			throws MissingObjectException, IOException {
286 		return update(name, ObjectId.zeroId(), id);
287 	}
288 
289 	private Command update(String name, ObjectId oldId, ObjectId newId)
290 			throws MissingObjectException, IOException {
291 		try (RevWalk rw = new RevWalk(repo)) {
292 			return new Command(rw, new ReceiveCommand(oldId, newId, name));
293 		}
294 	}
295 
296 	private ObjectId write(RefTree tree) throws IOException {
297 		try (ObjectInserter ins = repo.newObjectInserter()) {
298 			ObjectId id = tree.writeTree(ins);
299 			ins.flush();
300 			return id;
301 		}
302 	}
303 }