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.

GitIndex.java 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976
  1. /*
  2. * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
  3. * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
  4. * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
  5. * Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
  6. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  7. * and other copyright owners as documented in the project's IP log.
  8. *
  9. * This program and the accompanying materials are made available
  10. * under the terms of the Eclipse Distribution License v1.0 which
  11. * accompanies this distribution, is reproduced below, and is
  12. * available at http://www.eclipse.org/org/documents/edl-v10.php
  13. *
  14. * All rights reserved.
  15. *
  16. * Redistribution and use in source and binary forms, with or
  17. * without modification, are permitted provided that the following
  18. * conditions are met:
  19. *
  20. * - Redistributions of source code must retain the above copyright
  21. * notice, this list of conditions and the following disclaimer.
  22. *
  23. * - Redistributions in binary form must reproduce the above
  24. * copyright notice, this list of conditions and the following
  25. * disclaimer in the documentation and/or other materials provided
  26. * with the distribution.
  27. *
  28. * - Neither the name of the Eclipse Foundation, Inc. nor the
  29. * names of its contributors may be used to endorse or promote
  30. * products derived from this software without specific prior
  31. * written permission.
  32. *
  33. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  34. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  35. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  36. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  37. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  38. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  39. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  40. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  41. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  42. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  43. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  44. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  45. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  46. */
  47. package org.eclipse.jgit.lib;
  48. import java.io.File;
  49. import java.io.FileInputStream;
  50. import java.io.FileNotFoundException;
  51. import java.io.FileOutputStream;
  52. import java.io.IOException;
  53. import java.io.InputStream;
  54. import java.io.RandomAccessFile;
  55. import java.io.UnsupportedEncodingException;
  56. import java.nio.ByteBuffer;
  57. import java.nio.ByteOrder;
  58. import java.nio.channels.FileChannel;
  59. import java.security.MessageDigest;
  60. import java.util.Comparator;
  61. import java.util.Date;
  62. import java.util.Iterator;
  63. import java.util.Map;
  64. import java.util.Stack;
  65. import java.util.TreeMap;
  66. import org.eclipse.jgit.dircache.DirCache;
  67. import org.eclipse.jgit.errors.CorruptObjectException;
  68. import org.eclipse.jgit.errors.NotSupportedException;
  69. import org.eclipse.jgit.util.FS;
  70. import org.eclipse.jgit.util.RawParseUtils;
  71. /**
  72. * A representation of the Git index.
  73. *
  74. * The index points to the objects currently checked out or in the process of
  75. * being prepared for committing or objects involved in an unfinished merge.
  76. *
  77. * The abstract format is:<br/> path stage flags statdata SHA-1
  78. * <ul>
  79. * <li>Path is the relative path in the workdir</li>
  80. * <li>stage is 0 (normally), but when
  81. * merging 1 is the common ancestor version, 2 is 'our' version and 3 is 'their'
  82. * version. A fully resolved merge only contains stage 0.</li>
  83. * <li>flags is the object type and information of validity</li>
  84. * <li>statdata is the size of this object and some other file system specifics,
  85. * some of it ignored by JGit</li>
  86. * <li>SHA-1 represents the content of the references object</li>
  87. * </ul>
  88. *
  89. * An index can also contain a tree cache which we ignore for now. We drop the
  90. * tree cache when writing the index.
  91. *
  92. * @deprecated Use {@link DirCache} instead.
  93. */
  94. public class GitIndex {
  95. /** Stage 0 represents merged entries. */
  96. public static final int STAGE_0 = 0;
  97. private RandomAccessFile cache;
  98. private File cacheFile;
  99. // Index is modified
  100. private boolean changed;
  101. // Stat information updated
  102. private boolean statDirty;
  103. private Header header;
  104. private long lastCacheTime;
  105. private final Repository db;
  106. private Map<byte[], Entry> entries = new TreeMap<byte[], Entry>(new Comparator<byte[]>() {
  107. public int compare(byte[] o1, byte[] o2) {
  108. for (int i = 0; i < o1.length && i < o2.length; ++i) {
  109. int c = (o1[i] & 0xff) - (o2[i] & 0xff);
  110. if (c != 0)
  111. return c;
  112. }
  113. if (o1.length < o2.length)
  114. return -1;
  115. else if (o1.length > o2.length)
  116. return 1;
  117. return 0;
  118. }
  119. });
  120. /**
  121. * Construct a Git index representation.
  122. * @param db
  123. */
  124. public GitIndex(Repository db) {
  125. this.db = db;
  126. this.cacheFile = db.getIndexFile();
  127. }
  128. /**
  129. * @return true if we have modified the index in memory since reading it from disk
  130. */
  131. public boolean isChanged() {
  132. return changed || statDirty;
  133. }
  134. /**
  135. * Reread index data from disk if the index file has been changed
  136. * @throws IOException
  137. */
  138. public void rereadIfNecessary() throws IOException {
  139. if (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) {
  140. read();
  141. db.fireIndexChanged();
  142. }
  143. }
  144. /**
  145. * Add the content of a file to the index.
  146. *
  147. * @param wd workdir
  148. * @param f the file
  149. * @return a new or updated index entry for the path represented by f
  150. * @throws IOException
  151. */
  152. public Entry add(File wd, File f) throws IOException {
  153. byte[] key = makeKey(wd, f);
  154. Entry e = entries.get(key);
  155. if (e == null) {
  156. e = new Entry(key, f, 0);
  157. entries.put(key, e);
  158. } else {
  159. e.update(f);
  160. }
  161. return e;
  162. }
  163. /**
  164. * Add the content of a file to the index.
  165. *
  166. * @param wd
  167. * workdir
  168. * @param f
  169. * the file
  170. * @param content
  171. * content of the file
  172. * @return a new or updated index entry for the path represented by f
  173. * @throws IOException
  174. */
  175. public Entry add(File wd, File f, byte[] content) throws IOException {
  176. byte[] key = makeKey(wd, f);
  177. Entry e = entries.get(key);
  178. if (e == null) {
  179. e = new Entry(key, f, 0, content);
  180. entries.put(key, e);
  181. } else {
  182. e.update(f, content);
  183. }
  184. return e;
  185. }
  186. /**
  187. * Remove a path from the index.
  188. *
  189. * @param wd
  190. * workdir
  191. * @param f
  192. * the file whose path shall be removed.
  193. * @return true if such a path was found (and thus removed)
  194. * @throws IOException
  195. */
  196. public boolean remove(File wd, File f) throws IOException {
  197. byte[] key = makeKey(wd, f);
  198. return entries.remove(key) != null;
  199. }
  200. /**
  201. * Read the cache file into memory.
  202. *
  203. * @throws IOException
  204. */
  205. public void read() throws IOException {
  206. changed = false;
  207. statDirty = false;
  208. if (!cacheFile.exists()) {
  209. header = null;
  210. entries.clear();
  211. lastCacheTime = 0;
  212. return;
  213. }
  214. cache = new RandomAccessFile(cacheFile, "r");
  215. try {
  216. FileChannel channel = cache.getChannel();
  217. ByteBuffer buffer = ByteBuffer.allocateDirect((int) cacheFile.length());
  218. buffer.order(ByteOrder.BIG_ENDIAN);
  219. int j = channel.read(buffer);
  220. if (j != buffer.capacity())
  221. throw new IOException("Could not read index in one go, only "+j+" out of "+buffer.capacity()+" read");
  222. buffer.flip();
  223. header = new Header(buffer);
  224. entries.clear();
  225. for (int i = 0; i < header.entries; ++i) {
  226. Entry entry = new Entry(buffer);
  227. entries.put(entry.name, entry);
  228. }
  229. lastCacheTime = cacheFile.lastModified();
  230. } finally {
  231. cache.close();
  232. }
  233. }
  234. /**
  235. * Write content of index to disk.
  236. *
  237. * @throws IOException
  238. */
  239. public void write() throws IOException {
  240. checkWriteOk();
  241. File tmpIndex = new File(cacheFile.getAbsoluteFile() + ".tmp");
  242. File lock = new File(cacheFile.getAbsoluteFile() + ".lock");
  243. if (!lock.createNewFile())
  244. throw new IOException("Index file is in use");
  245. try {
  246. FileOutputStream fileOutputStream = new FileOutputStream(tmpIndex);
  247. FileChannel fc = fileOutputStream.getChannel();
  248. ByteBuffer buf = ByteBuffer.allocate(4096);
  249. MessageDigest newMessageDigest = Constants.newMessageDigest();
  250. header = new Header(entries);
  251. header.write(buf);
  252. buf.flip();
  253. newMessageDigest
  254. .update(buf.array(), buf.arrayOffset(), buf.limit());
  255. fc.write(buf);
  256. buf.flip();
  257. buf.clear();
  258. for (Iterator i = entries.values().iterator(); i.hasNext();) {
  259. Entry e = (Entry) i.next();
  260. e.write(buf);
  261. buf.flip();
  262. newMessageDigest.update(buf.array(), buf.arrayOffset(), buf
  263. .limit());
  264. fc.write(buf);
  265. buf.flip();
  266. buf.clear();
  267. }
  268. buf.put(newMessageDigest.digest());
  269. buf.flip();
  270. fc.write(buf);
  271. fc.close();
  272. fileOutputStream.close();
  273. if (cacheFile.exists())
  274. if (!cacheFile.delete())
  275. throw new IOException(
  276. "Could not rename delete old index");
  277. if (!tmpIndex.renameTo(cacheFile))
  278. throw new IOException(
  279. "Could not rename temporary index file to index");
  280. changed = false;
  281. statDirty = false;
  282. lastCacheTime = cacheFile.lastModified();
  283. db.fireIndexChanged();
  284. } finally {
  285. if (!lock.delete())
  286. throw new IOException(
  287. "Could not delete lock file. Should not happen");
  288. if (tmpIndex.exists() && !tmpIndex.delete())
  289. throw new IOException(
  290. "Could not delete temporary index file. Should not happen");
  291. }
  292. }
  293. private void checkWriteOk() throws IOException {
  294. for (Iterator i = entries.values().iterator(); i.hasNext();) {
  295. Entry e = (Entry) i.next();
  296. if (e.getStage() != 0) {
  297. throw new NotSupportedException("Cannot work with other stages than zero right now. Won't write corrupt index.");
  298. }
  299. }
  300. }
  301. static boolean File_canExecute( File f){
  302. return FS.INSTANCE.canExecute(f);
  303. }
  304. static boolean File_setExecute(File f, boolean value) {
  305. return FS.INSTANCE.setExecute(f, value);
  306. }
  307. static boolean File_hasExecute() {
  308. return FS.INSTANCE.supportsExecute();
  309. }
  310. static byte[] makeKey(File wd, File f) {
  311. if (!f.getPath().startsWith(wd.getPath()))
  312. throw new Error("Path is not in working dir");
  313. String relName = Repository.stripWorkDir(wd, f);
  314. return Constants.encode(relName);
  315. }
  316. Boolean filemode;
  317. private boolean config_filemode() {
  318. // temporary til we can actually set parameters. We need to be able
  319. // to change this for testing.
  320. if (filemode != null)
  321. return filemode.booleanValue();
  322. RepositoryConfig config = db.getConfig();
  323. return config.getBoolean("core", null, "filemode", true);
  324. }
  325. /** An index entry */
  326. public class Entry {
  327. private long ctime;
  328. private long mtime;
  329. private int dev;
  330. private int ino;
  331. private int mode;
  332. private int uid;
  333. private int gid;
  334. private int size;
  335. private ObjectId sha1;
  336. private short flags;
  337. private byte[] name;
  338. Entry(byte[] key, File f, int stage)
  339. throws IOException {
  340. ctime = f.lastModified() * 1000000L;
  341. mtime = ctime; // we use same here
  342. dev = -1;
  343. ino = -1;
  344. if (config_filemode() && File_canExecute(f))
  345. mode = FileMode.EXECUTABLE_FILE.getBits();
  346. else
  347. mode = FileMode.REGULAR_FILE.getBits();
  348. uid = -1;
  349. gid = -1;
  350. size = (int) f.length();
  351. ObjectWriter writer = new ObjectWriter(db);
  352. sha1 = writer.writeBlob(f);
  353. name = key;
  354. flags = (short) ((stage << 12) | name.length); // TODO: fix flags
  355. }
  356. Entry(byte[] key, File f, int stage, byte[] newContent)
  357. throws IOException {
  358. ctime = f.lastModified() * 1000000L;
  359. mtime = ctime; // we use same here
  360. dev = -1;
  361. ino = -1;
  362. if (config_filemode() && File_canExecute(f))
  363. mode = FileMode.EXECUTABLE_FILE.getBits();
  364. else
  365. mode = FileMode.REGULAR_FILE.getBits();
  366. uid = -1;
  367. gid = -1;
  368. size = newContent.length;
  369. ObjectWriter writer = new ObjectWriter(db);
  370. sha1 = writer.writeBlob(newContent);
  371. name = key;
  372. flags = (short) ((stage << 12) | name.length); // TODO: fix flags
  373. }
  374. Entry(TreeEntry f, int stage) {
  375. ctime = -1; // hmm
  376. mtime = -1;
  377. dev = -1;
  378. ino = -1;
  379. mode = f.getMode().getBits();
  380. uid = -1;
  381. gid = -1;
  382. try {
  383. size = (int) db.openBlob(f.getId()).getSize();
  384. } catch (IOException e) {
  385. e.printStackTrace();
  386. size = -1;
  387. }
  388. sha1 = f.getId();
  389. name = Constants.encode(f.getFullName());
  390. flags = (short) ((stage << 12) | name.length); // TODO: fix flags
  391. }
  392. Entry(ByteBuffer b) {
  393. int startposition = b.position();
  394. ctime = b.getInt() * 1000000000L + (b.getInt() % 1000000000L);
  395. mtime = b.getInt() * 1000000000L + (b.getInt() % 1000000000L);
  396. dev = b.getInt();
  397. ino = b.getInt();
  398. mode = b.getInt();
  399. uid = b.getInt();
  400. gid = b.getInt();
  401. size = b.getInt();
  402. byte[] sha1bytes = new byte[Constants.OBJECT_ID_LENGTH];
  403. b.get(sha1bytes);
  404. sha1 = ObjectId.fromRaw(sha1bytes);
  405. flags = b.getShort();
  406. name = new byte[flags & 0xFFF];
  407. b.get(name);
  408. b
  409. .position(startposition
  410. + ((8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 20 + 2
  411. + name.length + 8) & ~7));
  412. }
  413. /**
  414. * Update this index entry with stat and SHA-1 information if it looks
  415. * like the file has been modified in the workdir.
  416. *
  417. * @param f
  418. * file in work dir
  419. * @return true if a change occurred
  420. * @throws IOException
  421. */
  422. public boolean update(File f) throws IOException {
  423. long lm = f.lastModified() * 1000000L;
  424. boolean modified = mtime != lm;
  425. mtime = lm;
  426. if (size != f.length())
  427. modified = true;
  428. if (config_filemode()) {
  429. if (File_canExecute(f) != FileMode.EXECUTABLE_FILE.equals(mode)) {
  430. mode = FileMode.EXECUTABLE_FILE.getBits();
  431. modified = true;
  432. }
  433. }
  434. if (modified) {
  435. size = (int) f.length();
  436. ObjectWriter writer = new ObjectWriter(db);
  437. ObjectId newsha1 = writer.writeBlob(f);
  438. if (!newsha1.equals(sha1))
  439. modified = true;
  440. sha1 = newsha1;
  441. }
  442. return modified;
  443. }
  444. /**
  445. * Update this index entry with stat and SHA-1 information if it looks
  446. * like the file has been modified in the workdir.
  447. *
  448. * @param f
  449. * file in work dir
  450. * @param newContent
  451. * the new content of the file
  452. * @return true if a change occurred
  453. * @throws IOException
  454. */
  455. public boolean update(File f, byte[] newContent) throws IOException {
  456. boolean modified = false;
  457. size = newContent.length;
  458. ObjectWriter writer = new ObjectWriter(db);
  459. ObjectId newsha1 = writer.writeBlob(newContent);
  460. if (!newsha1.equals(sha1))
  461. modified = true;
  462. sha1 = newsha1;
  463. return modified;
  464. }
  465. void write(ByteBuffer buf) {
  466. int startposition = buf.position();
  467. buf.putInt((int) (ctime / 1000000000L));
  468. buf.putInt((int) (ctime % 1000000000L));
  469. buf.putInt((int) (mtime / 1000000000L));
  470. buf.putInt((int) (mtime % 1000000000L));
  471. buf.putInt(dev);
  472. buf.putInt(ino);
  473. buf.putInt(mode);
  474. buf.putInt(uid);
  475. buf.putInt(gid);
  476. buf.putInt(size);
  477. sha1.copyRawTo(buf);
  478. buf.putShort(flags);
  479. buf.put(name);
  480. int end = startposition
  481. + ((8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 20 + 2 + name.length + 8) & ~7);
  482. int remain = end - buf.position();
  483. while (remain-- > 0)
  484. buf.put((byte) 0);
  485. }
  486. /**
  487. * Check if an entry's content is different from the cache,
  488. *
  489. * File status information is used and status is same we
  490. * consider the file identical to the state in the working
  491. * directory. Native git uses more stat fields than we
  492. * have accessible in Java.
  493. *
  494. * @param wd working directory to compare content with
  495. * @return true if content is most likely different.
  496. */
  497. public boolean isModified(File wd) {
  498. return isModified(wd, false);
  499. }
  500. /**
  501. * Check if an entry's content is different from the cache,
  502. *
  503. * File status information is used and status is same we
  504. * consider the file identical to the state in the working
  505. * directory. Native git uses more stat fields than we
  506. * have accessible in Java.
  507. *
  508. * @param wd working directory to compare content with
  509. * @param forceContentCheck True if the actual file content
  510. * should be checked if modification time differs.
  511. *
  512. * @return true if content is most likely different.
  513. */
  514. public boolean isModified(File wd, boolean forceContentCheck) {
  515. if (isAssumedValid())
  516. return false;
  517. if (isUpdateNeeded())
  518. return true;
  519. File file = getFile(wd);
  520. if (!file.exists())
  521. return true;
  522. // JDK1.6 has file.canExecute
  523. // if (file.canExecute() != FileMode.EXECUTABLE_FILE.equals(mode))
  524. // return true;
  525. final int exebits = FileMode.EXECUTABLE_FILE.getBits()
  526. ^ FileMode.REGULAR_FILE.getBits();
  527. if (config_filemode() && FileMode.EXECUTABLE_FILE.equals(mode)) {
  528. if (!File_canExecute(file)&& File_hasExecute())
  529. return true;
  530. } else {
  531. if (FileMode.REGULAR_FILE.equals(mode&~exebits)) {
  532. if (!file.isFile())
  533. return true;
  534. if (config_filemode() && File_canExecute(file) && File_hasExecute())
  535. return true;
  536. } else {
  537. if (FileMode.SYMLINK.equals(mode)) {
  538. return true;
  539. } else {
  540. if (FileMode.TREE.equals(mode)) {
  541. if (!file.isDirectory())
  542. return true;
  543. } else {
  544. System.out.println("Does not handle mode "+mode+" ("+file+")");
  545. return true;
  546. }
  547. }
  548. }
  549. }
  550. if (file.length() != size)
  551. return true;
  552. // Git under windows only stores seconds so we round the timestamp
  553. // Java gives us if it looks like the timestamp in index is seconds
  554. // only. Otherwise we compare the timestamp at millisecond prevision.
  555. long javamtime = mtime / 1000000L;
  556. long lastm = file.lastModified();
  557. if (javamtime % 1000 == 0)
  558. lastm = lastm - lastm % 1000;
  559. if (lastm != javamtime) {
  560. if (!forceContentCheck)
  561. return true;
  562. try {
  563. InputStream is = new FileInputStream(file);
  564. try {
  565. ObjectWriter objectWriter = new ObjectWriter(db);
  566. ObjectId newId = objectWriter.computeBlobSha1(file
  567. .length(), is);
  568. boolean ret = !newId.equals(sha1);
  569. return ret;
  570. } catch (IOException e) {
  571. e.printStackTrace();
  572. } finally {
  573. try {
  574. is.close();
  575. } catch (IOException e) {
  576. // can't happen, but if it does we ignore it
  577. e.printStackTrace();
  578. }
  579. }
  580. } catch (FileNotFoundException e) {
  581. // should not happen because we already checked this
  582. e.printStackTrace();
  583. throw new Error(e);
  584. }
  585. }
  586. return false;
  587. }
  588. // for testing
  589. void forceRecheck() {
  590. mtime = -1;
  591. }
  592. private File getFile(File wd) {
  593. return new File(wd, getName());
  594. }
  595. public String toString() {
  596. return getName() + "/SHA-1(" + sha1.name() + ")/M:"
  597. + new Date(ctime / 1000000L) + "/C:"
  598. + new Date(mtime / 1000000L) + "/d" + dev + "/i" + ino
  599. + "/m" + Integer.toString(mode, 8) + "/u" + uid + "/g"
  600. + gid + "/s" + size + "/f" + flags + "/@" + getStage();
  601. }
  602. /**
  603. * @return path name for this entry
  604. */
  605. public String getName() {
  606. return RawParseUtils.decode(name);
  607. }
  608. /**
  609. * @return path name for this entry as byte array, hopefully UTF-8 encoded
  610. */
  611. public byte[] getNameUTF8() {
  612. return name;
  613. }
  614. /**
  615. * @return SHA-1 of the entry managed by this index
  616. */
  617. public ObjectId getObjectId() {
  618. return sha1;
  619. }
  620. /**
  621. * @return the stage this entry is in
  622. */
  623. public int getStage() {
  624. return (flags & 0x3000) >> 12;
  625. }
  626. /**
  627. * @return size of disk object
  628. */
  629. public int getSize() {
  630. return size;
  631. }
  632. /**
  633. * @return true if this entry shall be assumed valid
  634. */
  635. public boolean isAssumedValid() {
  636. return (flags & 0x8000) != 0;
  637. }
  638. /**
  639. * @return true if this entry should be checked for changes
  640. */
  641. public boolean isUpdateNeeded() {
  642. return (flags & 0x4000) != 0;
  643. }
  644. /**
  645. * Set whether to always assume this entry valid
  646. *
  647. * @param assumeValid true to ignore changes
  648. */
  649. public void setAssumeValid(boolean assumeValid) {
  650. if (assumeValid)
  651. flags |= 0x8000;
  652. else
  653. flags &= ~0x8000;
  654. }
  655. /**
  656. * Set whether this entry must be checked
  657. *
  658. * @param updateNeeded
  659. */
  660. public void setUpdateNeeded(boolean updateNeeded) {
  661. if (updateNeeded)
  662. flags |= 0x4000;
  663. else
  664. flags &= ~0x4000;
  665. }
  666. /**
  667. * Return raw file mode bits. See {@link FileMode}
  668. * @return file mode bits
  669. */
  670. public int getModeBits() {
  671. return mode;
  672. }
  673. }
  674. static class Header {
  675. private int signature;
  676. private int version;
  677. int entries;
  678. Header(ByteBuffer map) throws CorruptObjectException {
  679. read(map);
  680. }
  681. private void read(ByteBuffer buf) throws CorruptObjectException {
  682. signature = buf.getInt();
  683. version = buf.getInt();
  684. entries = buf.getInt();
  685. if (signature != 0x44495243)
  686. throw new CorruptObjectException("Index signature is invalid: "
  687. + signature);
  688. if (version != 2)
  689. throw new CorruptObjectException(
  690. "Unknown index version (or corrupt index):" + version);
  691. }
  692. void write(ByteBuffer buf) {
  693. buf.order(ByteOrder.BIG_ENDIAN);
  694. buf.putInt(signature);
  695. buf.putInt(version);
  696. buf.putInt(entries);
  697. }
  698. Header(Map entryset) {
  699. signature = 0x44495243;
  700. version = 2;
  701. entries = entryset.size();
  702. }
  703. }
  704. /**
  705. * Read a Tree recursively into the index
  706. *
  707. * @param t The tree to read
  708. *
  709. * @throws IOException
  710. */
  711. public void readTree(Tree t) throws IOException {
  712. entries.clear();
  713. readTree("", t);
  714. }
  715. void readTree(String prefix, Tree t) throws IOException {
  716. TreeEntry[] members = t.members();
  717. for (int i = 0; i < members.length; ++i) {
  718. TreeEntry te = members[i];
  719. String name;
  720. if (prefix.length() > 0)
  721. name = prefix + "/" + te.getName();
  722. else
  723. name = te.getName();
  724. if (te instanceof Tree) {
  725. readTree(name, (Tree) te);
  726. } else {
  727. Entry e = new Entry(te, 0);
  728. entries.put(Constants.encode(name), e);
  729. }
  730. }
  731. }
  732. /**
  733. * Add tree entry to index
  734. * @param te tree entry
  735. * @return new or modified index entry
  736. * @throws IOException
  737. */
  738. public Entry addEntry(TreeEntry te) throws IOException {
  739. byte[] key = Constants.encode(te.getFullName());
  740. Entry e = new Entry(te, 0);
  741. entries.put(key, e);
  742. return e;
  743. }
  744. /**
  745. * Check out content of the content represented by the index
  746. *
  747. * @param wd
  748. * workdir
  749. * @throws IOException
  750. */
  751. public void checkout(File wd) throws IOException {
  752. for (Entry e : entries.values()) {
  753. if (e.getStage() != 0)
  754. continue;
  755. checkoutEntry(wd, e);
  756. }
  757. }
  758. /**
  759. * Check out content of the specified index entry
  760. *
  761. * @param wd workdir
  762. * @param e index entry
  763. * @throws IOException
  764. */
  765. public void checkoutEntry(File wd, Entry e) throws IOException {
  766. ObjectLoader ol = db.openBlob(e.sha1);
  767. byte[] bytes = ol.getBytes();
  768. File file = new File(wd, e.getName());
  769. file.delete();
  770. file.getParentFile().mkdirs();
  771. FileChannel channel = new FileOutputStream(file).getChannel();
  772. ByteBuffer buffer = ByteBuffer.wrap(bytes);
  773. int j = channel.write(buffer);
  774. if (j != bytes.length)
  775. throw new IOException("Could not write file " + file);
  776. channel.close();
  777. if (config_filemode() && File_hasExecute()) {
  778. if (FileMode.EXECUTABLE_FILE.equals(e.mode)) {
  779. if (!File_canExecute(file))
  780. File_setExecute(file, true);
  781. } else {
  782. if (File_canExecute(file))
  783. File_setExecute(file, false);
  784. }
  785. }
  786. e.mtime = file.lastModified() * 1000000L;
  787. e.ctime = e.mtime;
  788. }
  789. /**
  790. * Construct and write tree out of index.
  791. *
  792. * @return SHA-1 of the constructed tree
  793. *
  794. * @throws IOException
  795. */
  796. public ObjectId writeTree() throws IOException {
  797. checkWriteOk();
  798. ObjectWriter writer = new ObjectWriter(db);
  799. Tree current = new Tree(db);
  800. Stack<Tree> trees = new Stack<Tree>();
  801. trees.push(current);
  802. String[] prevName = new String[0];
  803. for (Entry e : entries.values()) {
  804. if (e.getStage() != 0)
  805. continue;
  806. String[] newName = splitDirPath(e.getName());
  807. int c = longestCommonPath(prevName, newName);
  808. while (c < trees.size() - 1) {
  809. current.setId(writer.writeTree(current));
  810. trees.pop();
  811. current = trees.isEmpty() ? null : (Tree) trees.peek();
  812. }
  813. while (trees.size() < newName.length) {
  814. if (!current.existsTree(newName[trees.size() - 1])) {
  815. current = new Tree(current, Constants.encode(newName[trees.size() - 1]));
  816. current.getParent().addEntry(current);
  817. trees.push(current);
  818. } else {
  819. current = (Tree) current.findTreeMember(newName[trees
  820. .size() - 1]);
  821. trees.push(current);
  822. }
  823. }
  824. FileTreeEntry ne = new FileTreeEntry(current, e.sha1,
  825. Constants.encode(newName[newName.length - 1]),
  826. (e.mode & FileMode.EXECUTABLE_FILE.getBits()) == FileMode.EXECUTABLE_FILE.getBits());
  827. current.addEntry(ne);
  828. }
  829. while (!trees.isEmpty()) {
  830. current.setId(writer.writeTree(current));
  831. trees.pop();
  832. if (!trees.isEmpty())
  833. current = trees.peek();
  834. }
  835. return current.getTreeId();
  836. }
  837. String[] splitDirPath(String name) {
  838. String[] tmp = new String[name.length() / 2 + 1];
  839. int p0 = -1;
  840. int p1;
  841. int c = 0;
  842. while ((p1 = name.indexOf('/', p0 + 1)) != -1) {
  843. tmp[c++] = name.substring(p0 + 1, p1);
  844. p0 = p1;
  845. }
  846. tmp[c++] = name.substring(p0 + 1);
  847. String[] ret = new String[c];
  848. for (int i = 0; i < c; ++i) {
  849. ret[i] = tmp[i];
  850. }
  851. return ret;
  852. }
  853. int longestCommonPath(String[] a, String[] b) {
  854. int i;
  855. for (i = 0; i < a.length && i < b.length; ++i)
  856. if (!a[i].equals(b[i]))
  857. return i;
  858. return i;
  859. }
  860. /**
  861. * Return the members of the index sorted by the unsigned byte
  862. * values of the path names.
  863. *
  864. * Small beware: Unaccounted for are unmerged entries. You may want
  865. * to abort if members with stage != 0 are found if you are doing
  866. * any updating operations. All stages will be found after one another
  867. * here later. Currently only one stage per name is returned.
  868. *
  869. * @return The index entries sorted
  870. */
  871. public Entry[] getMembers() {
  872. return entries.values().toArray(new Entry[entries.size()]);
  873. }
  874. /**
  875. * Look up an entry with the specified path.
  876. *
  877. * @param path
  878. * @return index entry for the path or null if not in index.
  879. * @throws UnsupportedEncodingException
  880. */
  881. public Entry getEntry(String path) throws UnsupportedEncodingException {
  882. return entries.get(Repository.gitInternalSlash(Constants.encode(path)));
  883. }
  884. /**
  885. * @return The repository holding this index.
  886. */
  887. public Repository getRepository() {
  888. return db;
  889. }
  890. }