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 27KB

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