You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. /*
  2. * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
  3. * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  4. * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
  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. package org.eclipse.jgit.lib;
  46. import java.io.IOException;
  47. import org.eclipse.jgit.errors.CorruptObjectException;
  48. import org.eclipse.jgit.errors.EntryExistsException;
  49. import org.eclipse.jgit.errors.MissingObjectException;
  50. import org.eclipse.jgit.util.RawParseUtils;
  51. /**
  52. * A representation of a Git tree entry. A Tree is a directory in Git.
  53. */
  54. public class Tree extends TreeEntry implements Treeish {
  55. private static final TreeEntry[] EMPTY_TREE = {};
  56. /**
  57. * Compare two names represented as bytes. Since git treats names of trees and
  58. * blobs differently we have one parameter that represents a '/' for trees. For
  59. * other objects the value should be NUL. The names are compare by their positive
  60. * byte value (0..255).
  61. *
  62. * A blob and a tree with the same name will not compare equal.
  63. *
  64. * @param a name
  65. * @param b name
  66. * @param lasta '/' if a is a tree, else NUL
  67. * @param lastb '/' if b is a tree, else NUL
  68. *
  69. * @return < 0 if a is sorted before b, 0 if they are the same, else b
  70. */
  71. public static final int compareNames(final byte[] a, final byte[] b, final int lasta,final int lastb) {
  72. return compareNames(a, b, 0, b.length, lasta, lastb);
  73. }
  74. private static final int compareNames(final byte[] a, final byte[] nameUTF8,
  75. final int nameStart, final int nameEnd, final int lasta, int lastb) {
  76. int j,k;
  77. for (j = 0, k = nameStart; j < a.length && k < nameEnd; j++, k++) {
  78. final int aj = a[j] & 0xff;
  79. final int bk = nameUTF8[k] & 0xff;
  80. if (aj < bk)
  81. return -1;
  82. else if (aj > bk)
  83. return 1;
  84. }
  85. if (j < a.length) {
  86. int aj = a[j]&0xff;
  87. if (aj < lastb)
  88. return -1;
  89. else if (aj > lastb)
  90. return 1;
  91. else
  92. if (j == a.length - 1)
  93. return 0;
  94. else
  95. return -1;
  96. }
  97. if (k < nameEnd) {
  98. int bk = nameUTF8[k] & 0xff;
  99. if (lasta < bk)
  100. return -1;
  101. else if (lasta > bk)
  102. return 1;
  103. else
  104. if (k == nameEnd - 1)
  105. return 0;
  106. else
  107. return 1;
  108. }
  109. if (lasta < lastb)
  110. return -1;
  111. else if (lasta > lastb)
  112. return 1;
  113. final int namelength = nameEnd - nameStart;
  114. if (a.length == namelength)
  115. return 0;
  116. else if (a.length < namelength)
  117. return -1;
  118. else
  119. return 1;
  120. }
  121. private static final byte[] substring(final byte[] s, final int nameStart,
  122. final int nameEnd) {
  123. if (nameStart == 0 && nameStart == s.length)
  124. return s;
  125. final byte[] n = new byte[nameEnd - nameStart];
  126. System.arraycopy(s, nameStart, n, 0, n.length);
  127. return n;
  128. }
  129. private static final int binarySearch(final TreeEntry[] entries,
  130. final byte[] nameUTF8, final int nameUTF8last, final int nameStart, final int nameEnd) {
  131. if (entries.length == 0)
  132. return -1;
  133. int high = entries.length;
  134. int low = 0;
  135. do {
  136. final int mid = (low + high) >>> 1;
  137. final int cmp = compareNames(entries[mid].getNameUTF8(), nameUTF8,
  138. nameStart, nameEnd, TreeEntry.lastChar(entries[mid]), nameUTF8last);
  139. if (cmp < 0)
  140. low = mid + 1;
  141. else if (cmp == 0)
  142. return mid;
  143. else
  144. high = mid;
  145. } while (low < high);
  146. return -(low + 1);
  147. }
  148. private final Repository db;
  149. private TreeEntry[] contents;
  150. /**
  151. * Constructor for a new Tree
  152. *
  153. * @param repo The repository that owns the Tree.
  154. */
  155. public Tree(final Repository repo) {
  156. super(null, null, null);
  157. db = repo;
  158. contents = EMPTY_TREE;
  159. }
  160. /**
  161. * Construct a Tree object with known content and hash value
  162. *
  163. * @param repo
  164. * @param myId
  165. * @param raw
  166. * @throws IOException
  167. */
  168. public Tree(final Repository repo, final ObjectId myId, final byte[] raw)
  169. throws IOException {
  170. super(null, myId, null);
  171. db = repo;
  172. readTree(raw);
  173. }
  174. /**
  175. * Construct a new Tree under another Tree
  176. *
  177. * @param parent
  178. * @param nameUTF8
  179. */
  180. public Tree(final Tree parent, final byte[] nameUTF8) {
  181. super(parent, null, nameUTF8);
  182. db = parent.getRepository();
  183. contents = EMPTY_TREE;
  184. }
  185. /**
  186. * Construct a Tree with a known SHA-1 under another tree. Data is not yet
  187. * specified and will have to be loaded on demand.
  188. *
  189. * @param parent
  190. * @param id
  191. * @param nameUTF8
  192. */
  193. public Tree(final Tree parent, final ObjectId id, final byte[] nameUTF8) {
  194. super(parent, id, nameUTF8);
  195. db = parent.getRepository();
  196. }
  197. public FileMode getMode() {
  198. return FileMode.TREE;
  199. }
  200. /**
  201. * @return true if this Tree is the top level Tree.
  202. */
  203. public boolean isRoot() {
  204. return getParent() == null;
  205. }
  206. public Repository getRepository() {
  207. return db;
  208. }
  209. public final ObjectId getTreeId() {
  210. return getId();
  211. }
  212. public final Tree getTree() {
  213. return this;
  214. }
  215. /**
  216. * @return true of the data of this Tree is loaded
  217. */
  218. public boolean isLoaded() {
  219. return contents != null;
  220. }
  221. /**
  222. * Forget the in-memory data for this tree.
  223. */
  224. public void unload() {
  225. if (isModified())
  226. throw new IllegalStateException("Cannot unload a modified tree.");
  227. contents = null;
  228. }
  229. /**
  230. * Adds a new or existing file with the specified name to this tree.
  231. * Trees are added if necessary as the name may contain '/':s.
  232. *
  233. * @param name Name
  234. * @return a {@link FileTreeEntry} for the added file.
  235. * @throws IOException
  236. */
  237. public FileTreeEntry addFile(final String name) throws IOException {
  238. return addFile(Repository.gitInternalSlash(Constants.encode(name)), 0);
  239. }
  240. /**
  241. * Adds a new or existing file with the specified name to this tree.
  242. * Trees are added if necessary as the name may contain '/':s.
  243. *
  244. * @param s an array containing the name
  245. * @param offset when the name starts in the tree.
  246. *
  247. * @return a {@link FileTreeEntry} for the added file.
  248. * @throws IOException
  249. */
  250. public FileTreeEntry addFile(final byte[] s, final int offset)
  251. throws IOException {
  252. int slash;
  253. int p;
  254. for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
  255. // search for path component terminator
  256. }
  257. ensureLoaded();
  258. byte xlast = slash<s.length ? (byte)'/' : 0;
  259. p = binarySearch(contents, s, xlast, offset, slash);
  260. if (p >= 0 && slash < s.length && contents[p] instanceof Tree)
  261. return ((Tree) contents[p]).addFile(s, slash + 1);
  262. final byte[] newName = substring(s, offset, slash);
  263. if (p >= 0)
  264. throw new EntryExistsException(RawParseUtils.decode(newName));
  265. else if (slash < s.length) {
  266. final Tree t = new Tree(this, newName);
  267. insertEntry(p, t);
  268. return t.addFile(s, slash + 1);
  269. } else {
  270. final FileTreeEntry f = new FileTreeEntry(this, null, newName,
  271. false);
  272. insertEntry(p, f);
  273. return f;
  274. }
  275. }
  276. /**
  277. * Adds a new or existing Tree with the specified name to this tree.
  278. * Trees are added if necessary as the name may contain '/':s.
  279. *
  280. * @param name Name
  281. * @return a {@link FileTreeEntry} for the added tree.
  282. * @throws IOException
  283. */
  284. public Tree addTree(final String name) throws IOException {
  285. return addTree(Repository.gitInternalSlash(Constants.encode(name)), 0);
  286. }
  287. /**
  288. * Adds a new or existing Tree with the specified name to this tree.
  289. * Trees are added if necessary as the name may contain '/':s.
  290. *
  291. * @param s an array containing the name
  292. * @param offset when the name starts in the tree.
  293. *
  294. * @return a {@link FileTreeEntry} for the added tree.
  295. * @throws IOException
  296. */
  297. public Tree addTree(final byte[] s, final int offset) throws IOException {
  298. int slash;
  299. int p;
  300. for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
  301. // search for path component terminator
  302. }
  303. ensureLoaded();
  304. p = binarySearch(contents, s, (byte)'/', offset, slash);
  305. if (p >= 0 && slash < s.length && contents[p] instanceof Tree)
  306. return ((Tree) contents[p]).addTree(s, slash + 1);
  307. final byte[] newName = substring(s, offset, slash);
  308. if (p >= 0)
  309. throw new EntryExistsException(RawParseUtils.decode(newName));
  310. final Tree t = new Tree(this, newName);
  311. insertEntry(p, t);
  312. return slash == s.length ? t : t.addTree(s, slash + 1);
  313. }
  314. /**
  315. * Add the specified tree entry to this tree.
  316. *
  317. * @param e
  318. * @throws IOException
  319. */
  320. public void addEntry(final TreeEntry e) throws IOException {
  321. final int p;
  322. ensureLoaded();
  323. p = binarySearch(contents, e.getNameUTF8(), TreeEntry.lastChar(e), 0, e.getNameUTF8().length);
  324. if (p < 0) {
  325. e.attachParent(this);
  326. insertEntry(p, e);
  327. } else {
  328. throw new EntryExistsException(e.getName());
  329. }
  330. }
  331. private void insertEntry(int p, final TreeEntry e) {
  332. final TreeEntry[] c = contents;
  333. final TreeEntry[] n = new TreeEntry[c.length + 1];
  334. p = -(p + 1);
  335. for (int k = c.length - 1; k >= p; k--)
  336. n[k + 1] = c[k];
  337. n[p] = e;
  338. for (int k = p - 1; k >= 0; k--)
  339. n[k] = c[k];
  340. contents = n;
  341. setModified();
  342. }
  343. void removeEntry(final TreeEntry e) {
  344. final TreeEntry[] c = contents;
  345. final int p = binarySearch(c, e.getNameUTF8(), TreeEntry.lastChar(e), 0,
  346. e.getNameUTF8().length);
  347. if (p >= 0) {
  348. final TreeEntry[] n = new TreeEntry[c.length - 1];
  349. for (int k = c.length - 1; k > p; k--)
  350. n[k - 1] = c[k];
  351. for (int k = p - 1; k >= 0; k--)
  352. n[k] = c[k];
  353. contents = n;
  354. setModified();
  355. }
  356. }
  357. /**
  358. * @return number of members in this tree
  359. * @throws IOException
  360. */
  361. public int memberCount() throws IOException {
  362. ensureLoaded();
  363. return contents.length;
  364. }
  365. /**
  366. * Return all members of the tree sorted in Git order.
  367. *
  368. * Entries are sorted by the numerical unsigned byte
  369. * values with (sub)trees having an implicit '/'. An
  370. * example of a tree with three entries. a:b is an
  371. * actual file name here.
  372. *
  373. * <p>
  374. * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a.b
  375. * 040000 tree 4277b6e69d25e5efa77c455340557b384a4c018a a
  376. * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a:b
  377. *
  378. * @return all entries in this Tree, sorted.
  379. * @throws IOException
  380. */
  381. public TreeEntry[] members() throws IOException {
  382. ensureLoaded();
  383. final TreeEntry[] c = contents;
  384. if (c.length != 0) {
  385. final TreeEntry[] r = new TreeEntry[c.length];
  386. for (int k = c.length - 1; k >= 0; k--)
  387. r[k] = c[k];
  388. return r;
  389. } else
  390. return c;
  391. }
  392. private boolean exists(final String s, byte slast) throws IOException {
  393. return findMember(s, slast) != null;
  394. }
  395. /**
  396. * @param path to the tree.
  397. * @return true if a tree with the specified path can be found under this
  398. * tree.
  399. * @throws IOException
  400. */
  401. public boolean existsTree(String path) throws IOException {
  402. return exists(path,(byte)'/');
  403. }
  404. /**
  405. * @param path of the non-tree entry.
  406. * @return true if a blob, symlink, or gitlink with the specified name
  407. * can be found under this tree.
  408. * @throws IOException
  409. */
  410. public boolean existsBlob(String path) throws IOException {
  411. return exists(path,(byte)0);
  412. }
  413. private TreeEntry findMember(final String s, byte slast) throws IOException {
  414. return findMember(Repository.gitInternalSlash(Constants.encode(s)), slast, 0);
  415. }
  416. private TreeEntry findMember(final byte[] s, final byte slast, final int offset)
  417. throws IOException {
  418. int slash;
  419. int p;
  420. for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
  421. // search for path component terminator
  422. }
  423. ensureLoaded();
  424. byte xlast = slash<s.length ? (byte)'/' : slast;
  425. p = binarySearch(contents, s, xlast, offset, slash);
  426. if (p >= 0) {
  427. final TreeEntry r = contents[p];
  428. if (slash < s.length-1)
  429. return r instanceof Tree ? ((Tree) r).findMember(s, slast, slash + 1)
  430. : null;
  431. return r;
  432. }
  433. return null;
  434. }
  435. /**
  436. * @param s
  437. * blob name
  438. * @return a {@link TreeEntry} representing an object with the specified
  439. * relative path.
  440. * @throws IOException
  441. */
  442. public TreeEntry findBlobMember(String s) throws IOException {
  443. return findMember(s,(byte)0);
  444. }
  445. /**
  446. * @param s Tree Name
  447. * @return a Tree with the name s or null
  448. * @throws IOException
  449. */
  450. public TreeEntry findTreeMember(String s) throws IOException {
  451. return findMember(s,(byte)'/');
  452. }
  453. public void accept(final TreeVisitor tv, final int flags)
  454. throws IOException {
  455. final TreeEntry[] c;
  456. if ((MODIFIED_ONLY & flags) == MODIFIED_ONLY && !isModified())
  457. return;
  458. if ((LOADED_ONLY & flags) == LOADED_ONLY && !isLoaded()) {
  459. tv.startVisitTree(this);
  460. tv.endVisitTree(this);
  461. return;
  462. }
  463. ensureLoaded();
  464. tv.startVisitTree(this);
  465. if ((CONCURRENT_MODIFICATION & flags) == CONCURRENT_MODIFICATION)
  466. c = members();
  467. else
  468. c = contents;
  469. for (int k = 0; k < c.length; k++)
  470. c[k].accept(tv, flags);
  471. tv.endVisitTree(this);
  472. }
  473. private void ensureLoaded() throws IOException, MissingObjectException {
  474. if (!isLoaded()) {
  475. final ObjectLoader or = db.openTree(getId());
  476. if (or == null)
  477. throw new MissingObjectException(getId(), Constants.TYPE_TREE);
  478. readTree(or.getBytes());
  479. }
  480. }
  481. private void readTree(final byte[] raw) throws IOException {
  482. final int rawSize = raw.length;
  483. int rawPtr = 0;
  484. TreeEntry[] temp;
  485. int nextIndex = 0;
  486. while (rawPtr < rawSize) {
  487. while (rawPtr < rawSize && raw[rawPtr] != 0)
  488. rawPtr++;
  489. rawPtr++;
  490. rawPtr += Constants.OBJECT_ID_LENGTH;
  491. nextIndex++;
  492. }
  493. temp = new TreeEntry[nextIndex];
  494. rawPtr = 0;
  495. nextIndex = 0;
  496. while (rawPtr < rawSize) {
  497. int c = raw[rawPtr++];
  498. if (c < '0' || c > '7')
  499. throw new CorruptObjectException(getId(), "invalid entry mode");
  500. int mode = c - '0';
  501. for (;;) {
  502. c = raw[rawPtr++];
  503. if (' ' == c)
  504. break;
  505. else if (c < '0' || c > '7')
  506. throw new CorruptObjectException(getId(), "invalid mode");
  507. mode <<= 3;
  508. mode += c - '0';
  509. }
  510. int nameLen = 0;
  511. while (raw[rawPtr + nameLen] != 0)
  512. nameLen++;
  513. final byte[] name = new byte[nameLen];
  514. System.arraycopy(raw, rawPtr, name, 0, nameLen);
  515. rawPtr += nameLen + 1;
  516. final ObjectId id = ObjectId.fromRaw(raw, rawPtr);
  517. rawPtr += Constants.OBJECT_ID_LENGTH;
  518. final TreeEntry ent;
  519. if (FileMode.REGULAR_FILE.equals(mode))
  520. ent = new FileTreeEntry(this, id, name, false);
  521. else if (FileMode.EXECUTABLE_FILE.equals(mode))
  522. ent = new FileTreeEntry(this, id, name, true);
  523. else if (FileMode.TREE.equals(mode))
  524. ent = new Tree(this, id, name);
  525. else if (FileMode.SYMLINK.equals(mode))
  526. ent = new SymlinkTreeEntry(this, id, name);
  527. else if (FileMode.GITLINK.equals(mode))
  528. ent = new GitlinkTreeEntry(this, id, name);
  529. else
  530. throw new CorruptObjectException(getId(), "Invalid mode: "
  531. + Integer.toOctalString(mode));
  532. temp[nextIndex++] = ent;
  533. }
  534. contents = temp;
  535. }
  536. public String toString() {
  537. final StringBuffer r = new StringBuffer();
  538. r.append(ObjectId.toString(getId()));
  539. r.append(" T ");
  540. r.append(getFullName());
  541. return r.toString();
  542. }
  543. }