2 * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.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
.IOException
;
44 import org
.spearce
.jgit
.errors
.CorruptObjectException
;
45 import org
.spearce
.jgit
.errors
.EntryExistsException
;
46 import org
.spearce
.jgit
.errors
.MissingObjectException
;
49 * A representation of a Git tree entry. A Tree is a directory in Git.
51 public class Tree
extends TreeEntry
implements Treeish
{
52 private static final TreeEntry
[] EMPTY_TREE
= {};
55 * Compare two names represented as bytes. Since git treats names of trees and
56 * blobs differently we have one parameter that represents a '/' for trees. For
57 * other objects the value should be NUL. The names are compare by their positive
58 * byte value (0..255).
60 * A blob and a tree with the same name will not compare equal.
64 * @param lasta '/' if a is a tree, else NUL
65 * @param lastb '/' if b is a tree, else NUL
67 * @return < 0 if a is sorted before b, 0 if they are the same, else b
69 public static final int compareNames(final byte[] a
, final byte[] b
, final int lasta
,final int lastb
) {
70 return compareNames(a
, b
, 0, b
.length
, lasta
, lastb
);
73 private static final int compareNames(final byte[] a
, final byte[] nameUTF8
,
74 final int nameStart
, final int nameEnd
, final int lasta
, int lastb
) {
76 for (j
= 0, k
= nameStart
; j
< a
.length
&& k
< nameEnd
; j
++, k
++) {
77 final int aj
= a
[j
] & 0xff;
78 final int bk
= nameUTF8
[k
] & 0xff;
91 if (j
== a
.length
- 1)
97 int bk
= nameUTF8
[k
] & 0xff;
103 if (k
== nameEnd
- 1)
110 else if (lasta
> lastb
)
113 final int namelength
= nameEnd
- nameStart
;
114 if (a
.length
== namelength
)
116 else if (a
.length
< namelength
)
122 private static final byte[] substring(final byte[] s
, final int nameStart
,
124 if (nameStart
== 0 && nameStart
== s
.length
)
126 final byte[] n
= new byte[nameEnd
- nameStart
];
127 System
.arraycopy(s
, nameStart
, n
, 0, n
.length
);
131 private static final int binarySearch(final TreeEntry
[] entries
,
132 final byte[] nameUTF8
, final int nameUTF8last
, final int nameStart
, final int nameEnd
) {
133 if (entries
.length
== 0)
135 int high
= entries
.length
;
138 final int mid
= (low
+ high
) / 2;
139 final int cmp
= compareNames(entries
[mid
].getNameUTF8(), nameUTF8
,
140 nameStart
, nameEnd
, TreeEntry
.lastChar(entries
[mid
]), nameUTF8last
);
147 } while (low
< high
);
151 private final Repository db
;
153 private TreeEntry
[] contents
;
156 * Constructor for a new Tree
158 * @param repo The repository that owns the Tree.
160 public Tree(final Repository repo
) {
161 super(null, null, null);
163 contents
= EMPTY_TREE
;
167 * Construct a Tree object with known content and hash value
172 * @throws IOException
174 public Tree(final Repository repo
, final ObjectId myId
, final byte[] raw
)
176 super(null, myId
, null);
182 * Construct a new Tree under another Tree
187 public Tree(final Tree parent
, final byte[] nameUTF8
) {
188 super(parent
, null, nameUTF8
);
189 db
= parent
.getRepository();
190 contents
= EMPTY_TREE
;
194 * Construct a Tree with a known SHA-1 under another tree. Data is not yet
195 * specified and will have to be loaded on demand.
201 public Tree(final Tree parent
, final ObjectId id
, final byte[] nameUTF8
) {
202 super(parent
, id
, nameUTF8
);
203 db
= parent
.getRepository();
206 public FileMode
getMode() {
207 return FileMode
.TREE
;
211 * @return true if this Tree is the top level Tree.
213 public boolean isRoot() {
214 return getParent() == null;
217 public Repository
getRepository() {
221 public final ObjectId
getTreeId() {
225 public final Tree
getTree() {
230 * @return true of the data of this Tree is loaded
232 public boolean isLoaded() {
233 return contents
!= null;
237 * Forget the in-memory data for this tree.
239 public void unload() {
241 throw new IllegalStateException("Cannot unload a modified tree.");
246 * Adds a new or existing file with the specified name to this tree.
247 * Trees are added if necessary as the name may contain '/':s.
250 * @return a {@link FileTreeEntry} for the added file.
251 * @throws IOException
253 public FileTreeEntry
addFile(final String name
) throws IOException
{
254 return addFile(Repository
.gitInternalSlash(name
.getBytes(Constants
.CHARACTER_ENCODING
)), 0);
258 * Adds a new or existing file with the specified name to this tree.
259 * Trees are added if necessary as the name may contain '/':s.
261 * @param s an array containing the name
262 * @param offset when the name starts in the tree.
264 * @return a {@link FileTreeEntry} for the added file.
265 * @throws IOException
267 public FileTreeEntry
addFile(final byte[] s
, final int offset
)
272 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++) {
273 // search for path component terminator
277 byte xlast
= slash
<s
.length ?
(byte)'/' : 0;
278 p
= binarySearch(contents
, s
, xlast
, offset
, slash
);
279 if (p
>= 0 && slash
< s
.length
&& contents
[p
] instanceof Tree
)
280 return ((Tree
) contents
[p
]).addFile(s
, slash
+ 1);
282 final byte[] newName
= substring(s
, offset
, slash
);
284 throw new EntryExistsException(new String(newName
,
285 Constants
.CHARACTER_ENCODING
));
286 else if (slash
< s
.length
) {
287 final Tree t
= new Tree(this, newName
);
289 return t
.addFile(s
, slash
+ 1);
291 final FileTreeEntry f
= new FileTreeEntry(this, null, newName
,
299 * Adds a new or existing Tree with the specified name to this tree.
300 * Trees are added if necessary as the name may contain '/':s.
303 * @return a {@link FileTreeEntry} for the added tree.
304 * @throws IOException
306 public Tree
addTree(final String name
) throws IOException
{
307 return addTree(Repository
.gitInternalSlash(name
.getBytes(Constants
.CHARACTER_ENCODING
)), 0);
311 * Adds a new or existing Tree with the specified name to this tree.
312 * Trees are added if necessary as the name may contain '/':s.
314 * @param s an array containing the name
315 * @param offset when the name starts in the tree.
317 * @return a {@link FileTreeEntry} for the added tree.
318 * @throws IOException
320 public Tree
addTree(final byte[] s
, final int offset
) throws IOException
{
324 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++) {
325 // search for path component terminator
329 p
= binarySearch(contents
, s
, (byte)'/', offset
, slash
);
330 if (p
>= 0 && slash
< s
.length
&& contents
[p
] instanceof Tree
)
331 return ((Tree
) contents
[p
]).addTree(s
, slash
+ 1);
333 final byte[] newName
= substring(s
, offset
, slash
);
335 throw new EntryExistsException(new String(newName
,
336 Constants
.CHARACTER_ENCODING
));
338 final Tree t
= new Tree(this, newName
);
340 return slash
== s
.length ? t
: t
.addTree(s
, slash
+ 1);
344 * Add the specified tree entry to this tree.
347 * @throws IOException
349 public void addEntry(final TreeEntry e
) throws IOException
{
353 p
= binarySearch(contents
, e
.getNameUTF8(), TreeEntry
.lastChar(e
), 0, e
.getNameUTF8().length
);
355 e
.attachParent(this);
358 throw new EntryExistsException(new String(e
.getNameUTF8(),
359 Constants
.CHARACTER_ENCODING
));
363 private void insertEntry(int p
, final TreeEntry e
) {
364 final TreeEntry
[] c
= contents
;
365 final TreeEntry
[] n
= new TreeEntry
[c
.length
+ 1];
367 for (int k
= c
.length
- 1; k
>= p
; k
--)
370 for (int k
= p
- 1; k
>= 0; k
--)
376 void removeEntry(final TreeEntry e
) {
377 final TreeEntry
[] c
= contents
;
378 final int p
= binarySearch(c
, e
.getNameUTF8(), TreeEntry
.lastChar(e
), 0,
379 e
.getNameUTF8().length
);
381 final TreeEntry
[] n
= new TreeEntry
[c
.length
- 1];
382 for (int k
= c
.length
- 1; k
> p
; k
--)
384 for (int k
= p
- 1; k
>= 0; k
--)
392 * @return number of members in this tree
393 * @throws IOException
395 public int memberCount() throws IOException
{
397 return contents
.length
;
401 * Return all members of the tree sorted in Git order.
403 * Entries are sorted by the numerical unsigned byte
404 * values with (sub)trees having an implicit '/'. An
405 * example of a tree with three entries. a:b is an
406 * actual file name here.
409 * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a.b
410 * 040000 tree 4277b6e69d25e5efa77c455340557b384a4c018a a
411 * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a:b
413 * @return all entries in this Tree, sorted.
414 * @throws IOException
416 public TreeEntry
[] members() throws IOException
{
418 final TreeEntry
[] c
= contents
;
420 final TreeEntry
[] r
= new TreeEntry
[c
.length
];
421 for (int k
= c
.length
- 1; k
>= 0; k
--)
428 private boolean exists(final String s
, byte slast
) throws IOException
{
429 return findMember(s
, slast
) != null;
434 * @return true if a tree with the specified path can be found under this
436 * @throws IOException
438 public boolean existsTree(String path
) throws IOException
{
439 return exists(path
,(byte)'/');
444 * @return true if a blob or symlink with the specified name can be found
446 * @throws IOException
448 public boolean existsBlob(String path
) throws IOException
{
449 return exists(path
,(byte)0);
452 private TreeEntry
findMember(final String s
, byte slast
) throws IOException
{
453 return findMember(Repository
.gitInternalSlash(s
.getBytes(Constants
.CHARACTER_ENCODING
)), slast
, 0);
456 private TreeEntry
findMember(final byte[] s
, final byte slast
, final int offset
)
461 for (slash
= offset
; slash
< s
.length
&& s
[slash
] != '/'; slash
++) {
462 // search for path component terminator
466 byte xlast
= slash
<s
.length ?
(byte)'/' : slast
;
467 p
= binarySearch(contents
, s
, xlast
, offset
, slash
);
469 final TreeEntry r
= contents
[p
];
470 if (slash
< s
.length
-1)
471 return r
instanceof Tree ?
((Tree
) r
).findMember(s
, slast
, slash
+ 1)
481 * @return a {@link TreeEntry} representing an object with the specified
483 * @throws IOException
485 public TreeEntry
findBlobMember(String s
) throws IOException
{
486 return findMember(s
,(byte)0);
491 * @return a Tree with the name s or null
492 * @throws IOException
494 public TreeEntry
findTreeMember(String s
) throws IOException
{
495 return findMember(s
,(byte)'/');
498 public void accept(final TreeVisitor tv
, final int flags
)
502 if ((MODIFIED_ONLY
& flags
) == MODIFIED_ONLY
&& !isModified())
505 if ((LOADED_ONLY
& flags
) == LOADED_ONLY
&& !isLoaded()) {
506 tv
.startVisitTree(this);
507 tv
.endVisitTree(this);
512 tv
.startVisitTree(this);
514 if ((CONCURRENT_MODIFICATION
& flags
) == CONCURRENT_MODIFICATION
)
519 for (int k
= 0; k
< c
.length
; k
++)
520 c
[k
].accept(tv
, flags
);
522 tv
.endVisitTree(this);
525 private void ensureLoaded() throws IOException
, MissingObjectException
{
527 final ObjectLoader or
= db
.openTree(getId());
529 throw new MissingObjectException(getId(), Constants
.TYPE_TREE
);
530 readTree(or
.getBytes());
534 private void readTree(final byte[] raw
) throws IOException
{
535 final int rawSize
= raw
.length
;
540 while (rawPtr
< rawSize
) {
541 while (rawPtr
< rawSize
&& raw
[rawPtr
] != 0)
544 rawPtr
+= Constants
.OBJECT_ID_LENGTH
;
548 temp
= new TreeEntry
[nextIndex
];
551 while (rawPtr
< rawSize
) {
552 int c
= raw
[rawPtr
++];
553 if (c
< '0' || c
> '7')
554 throw new CorruptObjectException(getId(), "invalid entry mode");
560 else if (c
< '0' || c
> '7')
561 throw new CorruptObjectException(getId(), "invalid mode");
567 while (raw
[rawPtr
+ nameLen
] != 0)
569 final byte[] name
= new byte[nameLen
];
570 System
.arraycopy(raw
, rawPtr
, name
, 0, nameLen
);
571 rawPtr
+= nameLen
+ 1;
573 final ObjectId id
= ObjectId
.fromRaw(raw
, rawPtr
);
574 rawPtr
+= Constants
.OBJECT_ID_LENGTH
;
577 if (FileMode
.REGULAR_FILE
.equals(mode
))
578 ent
= new FileTreeEntry(this, id
, name
, false);
579 else if (FileMode
.EXECUTABLE_FILE
.equals(mode
))
580 ent
= new FileTreeEntry(this, id
, name
, true);
581 else if (FileMode
.TREE
.equals(mode
)) {
582 ent
= new Tree(this, id
, name
);
583 } else if (FileMode
.SYMLINK
.equals(mode
))
584 ent
= new SymlinkTreeEntry(this, id
, name
);
586 throw new CorruptObjectException(getId(), "Invalid mode: "
587 + Integer
.toOctalString(mode
));
588 temp
[nextIndex
++] = ent
;
594 public String
toString() {
595 final StringBuffer r
= new StringBuffer();
596 r
.append(ObjectId
.toString(getId()));
598 r
.append(getFullName());