2 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
4 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
8 * Redistribution and use in source and binary forms, with or
9 * without modification, are permitted provided that the following
12 * - Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * - Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials provided
18 * with the distribution.
20 * - Neither the name of the Git Development Community nor the
21 * names of its contributors may be used to endorse or promote
22 * products derived from this software without specific prior
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
26 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
27 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
30 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
32 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
37 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 package org
.spearce
.jgit
.lib
;
42 import java
.io
.ByteArrayInputStream
;
44 import java
.io
.IOException
;
45 import java
.io
.InputStream
;
46 import java
.util
.HashMap
;
48 import org
.spearce
.jgit
.errors
.CheckoutConflictException
;
50 public class ReadTreeTest
extends RepositoryTestCase
{
53 private Tree theMerge
;
54 private GitIndex theIndex
;
55 private WorkDirCheckout theReadTree
;
56 // Each of these rules are from the read-tree manpage
57 // go there to see what they mean.
58 // Rule 0 is left out for obvious reasons :)
59 public void testRules1thru3_NoIndexEntry() throws IOException
{
60 GitIndex index
= new GitIndex(db
);
62 Tree head
= new Tree(db
);
63 FileTreeEntry headFile
= head
.addFile("foo");
64 ObjectId objectId
= ObjectId
.fromString("ba78e065e2c261d4f7b8f42107588051e87e18e9");
65 headFile
.setId(objectId
);
66 Tree merge
= new Tree(db
);
68 WorkDirCheckout readTree
= new WorkDirCheckout(db
, trash
, head
, index
, merge
);
69 readTree
.prescanTwoTrees();
71 assertTrue(readTree
.removed
.contains("foo"));
73 readTree
= new WorkDirCheckout(db
, trash
, merge
, index
, head
);
74 readTree
.prescanTwoTrees();
76 assertEquals(objectId
, readTree
.updated
.get("foo"));
78 ObjectId anotherId
= ObjectId
.fromString("ba78e065e2c261d4f7b8f42107588051e87e18ee");
79 merge
.addFile("foo").setId(anotherId
);
81 readTree
= new WorkDirCheckout(db
, trash
, head
, index
, merge
);
82 readTree
.prescanTwoTrees();
84 assertEquals(anotherId
, readTree
.updated
.get("foo"));
87 void setupCase(HashMap
<String
, String
> headEntries
,
88 HashMap
<String
, String
> mergeEntries
,
89 HashMap
<String
, String
> indexEntries
) throws IOException
{
90 theHead
= buildTree(headEntries
);
91 theMerge
= buildTree(mergeEntries
);
92 theIndex
= buildIndex(indexEntries
);
95 private GitIndex
buildIndex(HashMap
<String
, String
> indexEntries
) throws IOException
{
96 GitIndex index
= new GitIndex(db
);
98 if (indexEntries
== null)
100 for (java
.util
.Map
.Entry
<String
,String
> e
: indexEntries
.entrySet()) {
101 index
.add(trash
, writeTrashFile(e
.getKey(), e
.getValue())).forceRecheck();
107 private Tree
buildTree(HashMap
<String
, String
> headEntries
) throws IOException
{
108 Tree tree
= new Tree(db
);
110 if (headEntries
== null)
112 for (java
.util
.Map
.Entry
<String
,String
> e
: headEntries
.entrySet()) {
113 tree
.addFile(e
.getKey()).setId(genSha1(e
.getValue()));
119 ObjectId
genSha1(String data
) {
120 InputStream is
= new ByteArrayInputStream(data
.getBytes());
121 ObjectWriter objectWriter
= new ObjectWriter(db
);
123 return objectWriter
.writeObject(Constants
.OBJ_BLOB
, data
124 .getBytes().length
, is
, true);
125 } catch (IOException e
) {
131 private WorkDirCheckout
go() throws IOException
{
132 theReadTree
= new WorkDirCheckout(db
, trash
, theHead
, theIndex
, theMerge
);
133 theReadTree
.prescanTwoTrees();
137 // for these rules, they all have clean yes/no options
138 // but it doesn't matter if the entry is clean or not
139 // so we can just ignore the state in the filesystem entirely
140 public void testRules4thru13_IndexEntryNotInHead() throws IOException
{
142 HashMap
<String
, String
> idxMap
;
144 idxMap
= new HashMap
<String
, String
>();
145 idxMap
.put("foo", "foo");
146 setupCase(null, null, idxMap
);
149 assertTrue(theReadTree
.updated
.isEmpty());
150 assertTrue(theReadTree
.removed
.isEmpty());
151 assertTrue(theReadTree
.conflicts
.isEmpty());
154 idxMap
= new HashMap
<String
, String
>();
155 idxMap
.put("foo", "foo");
156 setupCase(null, idxMap
, idxMap
);
162 HashMap
<String
, String
> mergeMap
;
163 mergeMap
= new HashMap
<String
, String
>();
165 mergeMap
.put("foo", "merge");
166 setupCase(null, mergeMap
, idxMap
);
169 assertTrue(theReadTree
.updated
.isEmpty());
170 assertTrue(theReadTree
.removed
.isEmpty());
171 assertTrue(theReadTree
.conflicts
.contains("foo"));
175 HashMap
<String
, String
> headMap
= new HashMap
<String
, String
>();
176 headMap
.put("foo", "foo");
177 setupCase(headMap
, null, idxMap
);
180 assertTrue(theReadTree
.removed
.contains("foo"));
181 assertTrue(theReadTree
.updated
.isEmpty());
182 assertTrue(theReadTree
.conflicts
.isEmpty());
185 setupCase(headMap
, null, idxMap
);
186 new File(trash
, "foo").delete();
187 writeTrashFile("foo", "bar");
188 theIndex
.getMembers()[0].forceRecheck();
191 assertTrue(theReadTree
.removed
.isEmpty());
192 assertTrue(theReadTree
.updated
.isEmpty());
193 assertTrue(theReadTree
.conflicts
.contains("foo"));
196 headMap
.put("foo", "head");
197 setupCase(headMap
, null, idxMap
);
200 assertTrue(theReadTree
.removed
.isEmpty());
201 assertTrue(theReadTree
.updated
.isEmpty());
202 assertTrue(theReadTree
.conflicts
.contains("foo"));
205 setupCase(headMap
, headMap
, idxMap
);
211 setupCase(headMap
, mergeMap
, idxMap
); go();
212 assertTrue(theReadTree
.conflicts
.contains("foo"));
215 setupCase(headMap
, idxMap
, idxMap
); go();
219 setupCase(idxMap
, mergeMap
, idxMap
); go();
220 assertTrue(theReadTree
.updated
.containsKey("foo"));
223 setupCase(idxMap
, mergeMap
, idxMap
);
224 new File(trash
, "foo").delete();
225 writeTrashFile("foo", "bar");
226 theIndex
.getMembers()[0].forceRecheck();
228 assertTrue(theReadTree
.conflicts
.contains("foo"));
231 private void assertAllEmpty() {
232 assertTrue(theReadTree
.removed
.isEmpty());
233 assertTrue(theReadTree
.updated
.isEmpty());
234 assertTrue(theReadTree
.conflicts
.isEmpty());
237 public void testDirectoryFileSimple() throws IOException
{
238 theIndex
= new GitIndex(db
);
239 theIndex
.add(trash
, writeTrashFile("DF", "DF"));
240 Tree treeDF
= db
.mapTree(theIndex
.writeTree());
242 recursiveDelete(new File(trash
, "DF"));
243 theIndex
= new GitIndex(db
);
244 theIndex
.add(trash
, writeTrashFile("DF/DF", "DF/DF"));
245 Tree treeDFDF
= db
.mapTree(theIndex
.writeTree());
247 theIndex
= new GitIndex(db
);
248 recursiveDelete(new File(trash
, "DF"));
250 theIndex
.add(trash
, writeTrashFile("DF", "DF"));
251 theReadTree
= new WorkDirCheckout(db
, trash
, treeDF
, theIndex
, treeDFDF
);
252 theReadTree
.prescanTwoTrees();
254 assertTrue(theReadTree
.removed
.contains("DF"));
255 assertTrue(theReadTree
.updated
.containsKey("DF/DF"));
257 recursiveDelete(new File(trash
, "DF"));
258 theIndex
= new GitIndex(db
);
259 theIndex
.add(trash
, writeTrashFile("DF/DF", "DF/DF"));
261 theReadTree
= new WorkDirCheckout(db
, trash
, treeDFDF
, theIndex
, treeDF
);
262 theReadTree
.prescanTwoTrees();
263 assertTrue(theReadTree
.removed
.contains("DF/DF"));
264 assertTrue(theReadTree
.updated
.containsKey("DF"));
268 * Directory/File Conflict cases:
269 * It's entirely possible that in practice a number of these may be equivalent
270 * to the cases described in git-read-tree.txt. As long as it does the right thing,
271 * that's all I care about. These are basically reverse-engineered from
272 * what git currently does. If there are tests for these in git, it's kind of
273 * hard to track them all down...
275 * H I M Clean H==M H==I I==M Result
276 * ------------------------------------------------------------------
277 *1 D D F Y N Y N Update
278 *2 D D F N N Y N Conflict
279 *3 D F D Y N N Update
280 *4 D F D N N N Update
281 *5 D F F Y N N Y Keep
282 *6 D F F N N N Y Keep
283 *7 F D F Y Y N N Update
284 *8 F D F N Y N N Conflict
285 *9 F D F Y N N N Update
287 *11 F D D N N N Conflict
288 *12 F F D Y N Y N Update
289 *13 F F D N N Y N Conflict
290 *14 F F D N N N Conflict
291 *15 0 F D N N N Conflict
292 *16 0 D F Y N N N Update
293 *17 0 D F N N N Conflict
298 public void testDirectoryFileConflicts_1() throws Exception
{
300 doit(mk("DF/DF"), mk("DF"), mk("DF/DF"));
303 assertRemoved("DF/DF");
306 public void testDirectoryFileConflicts_2() throws Exception
{
308 setupCase(mk("DF/DF"), mk("DF"), mk("DF/DF"));
309 writeTrashFile("DF/DF", "different");
311 assertConflict("DF/DF");
315 public void testDirectoryFileConflicts_3() throws Exception
{
316 // 3 - the first to break!
317 doit(mk("DF/DF"), mk("DF/DF"), mk("DF"));
318 assertUpdated("DF/DF");
322 public void testDirectoryFileConflicts_4() throws Exception
{
323 // 4 (basically same as 3, just with H and M different)
324 doit(mk("DF/DF"), mkmap("DF/DF", "foo"), mk("DF"));
325 assertUpdated("DF/DF");
330 public void testDirectoryFileConflicts_5() throws Exception
{
332 doit(mk("DF/DF"), mk("DF"), mk("DF"));
333 assertRemoved("DF/DF");
337 public void testDirectoryFileConflicts_6() throws Exception
{
339 setupCase(mk("DF/DF"), mk("DF"), mk("DF"));
340 writeTrashFile("DF", "different");
342 assertRemoved("DF/DF");
345 public void testDirectoryFileConflicts_7() throws Exception
{
347 doit(mk("DF"), mk("DF"), mk("DF/DF"));
349 assertRemoved("DF/DF");
352 setupCase(mk("DF/DF"), mk("DF/DF"), mk("DF/DF/DF/DF/DF"));
354 assertRemoved("DF/DF/DF/DF/DF");
355 assertUpdated("DF/DF");
358 setupCase(mk("DF/DF"), mk("DF/DF"), mk("DF/DF/DF/DF/DF"));
359 writeTrashFile("DF/DF/DF/DF/DF", "diff");
361 assertConflict("DF/DF/DF/DF/DF");
362 assertUpdated("DF/DF");
368 public void testDirectoryFileConflicts_9() throws Exception
{
370 doit(mk("DF"), mkmap("DF", "QP"), mk("DF/DF"));
371 assertRemoved("DF/DF");
375 public void testDirectoryFileConflicts_10() throws Exception
{
378 doit(mk("DF"), mk("DF/DF"), mk("DF/DF"));
383 public void testDirectoryFileConflicts_11() throws Exception
{
385 doit(mk("DF"), mk("DF/DF"), mkmap("DF/DF", "asdf"));
386 assertConflict("DF/DF");
389 public void testDirectoryFileConflicts_12() throws Exception
{
392 doit(mk("DF"), mk("DF/DF"), mk("DF"));
394 assertUpdated("DF/DF");
397 public void testDirectoryFileConflicts_13() throws Exception
{
400 setupCase(mk("DF"), mk("DF/DF"), mk("DF"));
401 writeTrashFile("DF", "asdfsdf");
403 assertConflict("DF");
404 assertUpdated("DF/DF");
407 public void testDirectoryFileConflicts_14() throws Exception
{
410 doit(mk("DF"), mk("DF/DF"), mkmap("DF", "Foo"));
411 assertConflict("DF");
412 assertUpdated("DF/DF");
415 public void testDirectoryFileConflicts_15() throws Exception
{
417 doit(mkmap(), mk("DF/DF"), mk("DF"));
419 assertUpdated("DF/DF");
422 public void testDirectoryFileConflicts_15b() throws Exception
{
423 // 15, take 2, just to check multi-leveled
424 doit(mkmap(), mk("DF/DF/DF/DF"), mk("DF"));
426 assertUpdated("DF/DF/DF/DF");
429 public void testDirectoryFileConflicts_16() throws Exception
{
432 doit(mkmap(), mk("DF"), mk("DF/DF/DF"));
433 assertRemoved("DF/DF/DF");
437 public void testDirectoryFileConflicts_17() throws Exception
{
440 setupCase(mkmap(), mk("DF"), mk("DF/DF/DF"));
441 writeTrashFile("DF/DF/DF", "asdf");
443 assertConflict("DF/DF/DF");
447 public void testDirectoryFileConflicts_18() throws Exception
{
450 doit(mk("DF/DF"), mk("DF/DF/DF/DF"), null);
451 assertRemoved("DF/DF");
452 assertUpdated("DF/DF/DF/DF");
455 public void testDirectoryFileConflicts_19() throws Exception
{
458 doit(mk("DF/DF/DF/DF"), mk("DF/DF/DF"), null);
459 assertRemoved("DF/DF/DF/DF");
460 assertUpdated("DF/DF/DF");
463 private void cleanUpDF() throws Exception
{
466 recursiveDelete(new File(trash
, "DF"));
469 private void assertConflict(String s
) {
470 assertTrue(theReadTree
.conflicts
.contains(s
));
473 private void assertUpdated(String s
) {
474 assertTrue(theReadTree
.updated
.containsKey(s
));
477 private void assertRemoved(String s
) {
478 assertTrue(theReadTree
.removed
.contains(s
));
481 private void assertNoConflicts() {
482 assertTrue(theReadTree
.conflicts
.isEmpty());
485 private void doit(HashMap
<String
, String
> h
, HashMap
<String
, String
>m
,
486 HashMap
<String
, String
> i
) throws IOException
{
491 private static HashMap
<String
, String
> mk(String a
) {
495 private static HashMap
<String
, String
> mkmap(String
... args
) {
496 if ((args
.length
% 2) > 0)
497 throw new IllegalArgumentException("needs to be pairs");
499 HashMap
<String
, String
> map
= new HashMap
<String
, String
>();
500 for (int i
= 0; i
< args
.length
; i
+= 2) {
501 map
.put(args
[i
], args
[i
+1]);
507 public void testUntrackedConflicts() throws IOException
{
508 setupCase(null, mk("foo"), null);
509 writeTrashFile("foo", "foo");
512 assertConflict("foo");
514 recursiveDelete(new File(trash
, "foo"));
515 setupCase(null, mk("foo"), null);
516 writeTrashFile("foo/bar/baz", "");
517 writeTrashFile("foo/blahblah", "");
520 assertConflict("foo/bar/baz");
521 assertConflict("foo/blahblah");
523 recursiveDelete(new File(trash
, "foo"));
525 setupCase(mkmap("foo/bar", "", "foo/baz", ""),
526 mk("foo"), mkmap("foo/bar", "", "foo/baz", ""));
527 assertTrue(new File(trash
, "foo/bar").exists());
533 public void testCloseNameConflictsX0() throws IOException
{
534 setupCase(mkmap("a/a", "a/a-c"), mkmap("a/a","a/a", "b.b/b.b","b.b/b.bs"), mkmap("a/a", "a/a-c") );
540 public void testCloseNameConflicts1() throws IOException
{
541 setupCase(mkmap("a/a", "a/a-c"), mkmap("a/a","a/a", "a.a/a.a","a.a/a.a"), mkmap("a/a", "a/a-c") );
547 private void checkout() throws IOException
{
548 theReadTree
= new WorkDirCheckout(db
, trash
, theHead
, theIndex
, theMerge
);
549 theReadTree
.checkout();
552 public void testCheckoutOutChanges() throws IOException
{
553 setupCase(mk("foo"), mk("foo/bar"), mk("foo"));
556 assertFalse(new File(trash
, "foo").isFile());
557 assertTrue(new File(trash
, "foo/bar").isFile());
558 recursiveDelete(new File(trash
, "foo"));
560 setupCase(mk("foo/bar"), mk("foo"), mk("foo/bar"));
563 assertFalse(new File(trash
, "foo/bar").isFile());
564 assertTrue(new File(trash
, "foo").isFile());
566 setupCase(mk("foo"), mkmap("foo", "qux"), mkmap("foo", "bar"));
570 fail("did not throw exception");
571 } catch (CheckoutConflictException e
) {
572 // should have thrown