Switch jgit library to the EDL (3-clause BSD)
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / GitIndex.java
blob070d1b9fc60f793eb551f126ea77baf7daadaa62
1 /*
2 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3 * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
4 * Copyright (C) 2008, 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>
8 * All rights reserved.
10 * Redistribution and use in source and binary forms, with or
11 * without modification, are permitted provided that the following
12 * conditions are met:
14 * - Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
17 * - Redistributions in binary form must reproduce the above
18 * copyright notice, this list of conditions and the following
19 * disclaimer in the documentation and/or other materials provided
20 * with the distribution.
22 * - Neither the name of the Git Development Community nor the
23 * names of its contributors may be used to endorse or promote
24 * products derived from this software without specific prior
25 * written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
28 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
29 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
30 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
32 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
34 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
35 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
36 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
39 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 package org.spearce.jgit.lib;
44 import java.io.File;
45 import java.io.FileInputStream;
46 import java.io.FileNotFoundException;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.RandomAccessFile;
51 import java.io.UnsupportedEncodingException;
52 import java.nio.ByteBuffer;
53 import java.nio.ByteOrder;
54 import java.nio.channels.FileChannel;
55 import java.security.MessageDigest;
56 import java.util.Comparator;
57 import java.util.Date;
58 import java.util.Iterator;
59 import java.util.Map;
60 import java.util.Stack;
61 import java.util.TreeMap;
63 import org.spearce.jgit.errors.CorruptObjectException;
64 import org.spearce.jgit.errors.NotSupportedException;
65 import org.spearce.jgit.util.FS;
67 /**
68 * A representation of the Git index.
70 * The index points to the objects currently checked out or in the process of
71 * being prepared for committing or objects involved in an unfinished merge.
73 * The abstract format is:<br/> path stage flags statdata SHA-1
74 * <ul>
75 * <li>Path is the relative path in the workdir</li>
76 * <li>stage is 0 (normally), but when
77 * merging 1 is the common ancestor version, 2 is 'our' version and 3 is 'their'
78 * version. A fully resolved merge only contains stage 0.</li>
79 * <li>flags is the object type and information of validity</li>
80 * <li>statdata is the size of this object and some other file system specifics,
81 * some of it ignored by JGit</li>
82 * <li>SHA-1 represents the content of the references object</li>
83 * </ul>
85 * An index can also contain a tree cache which we ignore for now. We drop the
86 * tree cache when writing the index.
88 public class GitIndex {
90 /** Stage 0 represents merged entries. */
91 public static final int STAGE_0 = 0;
93 private RandomAccessFile cache;
95 private File cacheFile;
97 // Index is modified
98 private boolean changed;
100 // Stat information updated
101 private boolean statDirty;
103 private Header header;
105 private long lastCacheTime;
107 private final Repository db;
109 private Map entries = new TreeMap(new Comparator() {
110 public int compare(Object arg0, Object arg1) {
111 byte[] a = (byte[]) arg0;
112 byte[] b = (byte[]) arg1;
113 for (int i = 0; i < a.length && i < b.length; ++i) {
114 int c = a[i] - b[i];
115 if (c != 0)
116 return c;
118 if (a.length < b.length)
119 return -1;
120 else if (a.length > b.length)
121 return 1;
122 return 0;
127 * Construct a Git index representation.
128 * @param db
130 public GitIndex(Repository db) {
131 this.db = db;
132 this.cacheFile = new File(db.getDirectory(), "index");
136 * @return true if we have modified the index in memory since reading it from disk
138 public boolean isChanged() {
139 return changed || statDirty;
143 * Reread index data from disk if the index file has been changed
144 * @throws IOException
146 public void rereadIfNecessary() throws IOException {
147 if (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) {
148 read();
153 * Add the content of a file to the index.
155 * @param wd workdir
156 * @param f the file
157 * @return a new or updated index entry for the path represented by f
158 * @throws IOException
160 public Entry add(File wd, File f) throws IOException {
161 byte[] key = makeKey(wd, f);
162 Entry e = (Entry) entries.get(key);
163 if (e == null) {
164 e = new Entry(key, f, 0);
165 entries.put(key, e);
166 } else {
167 e.update(f);
169 return e;
173 * Remove a path from the index.
175 * @param wd
176 * workdir
177 * @param f
178 * the file whose path shall be removed.
179 * @return true if such a path was found (and thus removed)
181 public boolean remove(File wd, File f) {
182 byte[] key = makeKey(wd, f);
183 return entries.remove(key) != null;
187 * Read the cache file into memory.
189 * @throws IOException
191 public void read() throws IOException {
192 long t0 = System.currentTimeMillis();
193 changed = false;
194 statDirty = false;
195 if (!cacheFile.exists()) {
196 header = null;
197 entries.clear();
198 lastCacheTime = 0;
199 return;
201 cache = new RandomAccessFile(cacheFile, "r");
202 try {
203 FileChannel channel = cache.getChannel();
204 ByteBuffer buffer = ByteBuffer.allocateDirect((int) cacheFile.length());
205 buffer.order(ByteOrder.BIG_ENDIAN);
206 int j = channel.read(buffer);
207 if (j != buffer.capacity())
208 throw new IOException("Could not read index in one go, only "+j+" out of "+buffer.capacity()+" read");
209 buffer.flip();
210 header = new Header(buffer);
211 entries.clear();
212 for (int i = 0; i < header.entries; ++i) {
213 Entry entry = new Entry(buffer);
214 entries.put(entry.name, entry);
216 long t1 = System.currentTimeMillis();
217 lastCacheTime = cacheFile.lastModified();
218 System.out.println("Read index "+cacheFile+" in "+((t1-t0)/1000.0)+"s");
219 } finally {
220 cache.close();
225 * Write content of index to disk.
227 * @throws IOException
229 public void write() throws IOException {
230 checkWriteOk();
231 File tmpIndex = new File(cacheFile.getAbsoluteFile() + ".tmp");
232 File lock = new File(cacheFile.getAbsoluteFile() + ".lock");
233 if (!lock.createNewFile())
234 throw new IOException("Index file is in use");
235 try {
236 FileOutputStream fileOutputStream = new FileOutputStream(tmpIndex);
237 FileChannel fc = fileOutputStream.getChannel();
238 ByteBuffer buf = ByteBuffer.allocate(4096);
239 MessageDigest newMessageDigest = Constants.newMessageDigest();
240 header = new Header(entries);
241 header.write(buf);
242 buf.flip();
243 newMessageDigest
244 .update(buf.array(), buf.arrayOffset(), buf.limit());
245 fc.write(buf);
246 buf.flip();
247 buf.clear();
248 for (Iterator i = entries.values().iterator(); i.hasNext();) {
249 Entry e = (Entry) i.next();
250 e.write(buf);
251 buf.flip();
252 newMessageDigest.update(buf.array(), buf.arrayOffset(), buf
253 .limit());
254 fc.write(buf);
255 buf.flip();
256 buf.clear();
258 buf.put(newMessageDigest.digest());
259 buf.flip();
260 fc.write(buf);
261 fc.close();
262 fileOutputStream.close();
263 if (cacheFile.exists())
264 if (!cacheFile.delete())
265 throw new IOException(
266 "Could not rename delete old index");
267 if (!tmpIndex.renameTo(cacheFile))
268 throw new IOException(
269 "Could not rename temporary index file to index");
270 changed = false;
271 statDirty = false;
272 } finally {
273 if (!lock.delete())
274 throw new IOException(
275 "Could not delete lock file. Should not happen");
276 if (tmpIndex.exists() && !tmpIndex.delete())
277 throw new IOException(
278 "Could not delete temporary index file. Should not happen");
282 private void checkWriteOk() throws IOException {
283 for (Iterator i = entries.values().iterator(); i.hasNext();) {
284 Entry e = (Entry) i.next();
285 if (e.getStage() != 0) {
286 throw new NotSupportedException("Cannot work with other stages than zero right now. Won't write corrupt index.");
291 static boolean File_canExecute( File f){
292 return FS.INSTANCE.canExecute(f);
295 static boolean File_setExecute(File f, boolean value) {
296 return FS.INSTANCE.setExecute(f, value);
299 static boolean File_hasExecute() {
300 return FS.INSTANCE.supportsExecute();
303 static byte[] makeKey(File wd, File f) {
304 if (!f.getPath().startsWith(wd.getPath()))
305 throw new Error("Path is not in working dir");
306 String relName = Repository.stripWorkDir(wd, f);
307 return relName.getBytes();
310 Boolean filemode;
311 private boolean config_filemode() {
312 // temporary til we can actually set parameters. We need to be able
313 // to change this for testing.
314 if (filemode != null)
315 return filemode.booleanValue();
316 RepositoryConfig config = db.getConfig();
317 return config.getBoolean("core", null, "filemode", true);
320 /** An index entry */
321 public class Entry {
322 private long ctime;
324 private long mtime;
326 private int dev;
328 private int ino;
330 private int mode;
332 private int uid;
334 private int gid;
336 private int size;
338 private ObjectId sha1;
340 private short flags;
342 private byte[] name;
344 Entry(byte[] key, File f, int stage)
345 throws IOException {
346 ctime = f.lastModified() * 1000000L;
347 mtime = ctime; // we use same here
348 dev = -1;
349 ino = -1;
350 if (config_filemode() && File_canExecute(f))
351 mode = FileMode.EXECUTABLE_FILE.getBits();
352 else
353 mode = FileMode.REGULAR_FILE.getBits();
354 uid = -1;
355 gid = -1;
356 size = (int) f.length();
357 ObjectWriter writer = new ObjectWriter(db);
358 sha1 = writer.writeBlob(f);
359 name = key;
360 flags = (short) ((stage << 12) | name.length); // TODO: fix flags
363 Entry(TreeEntry f, int stage)
364 throws UnsupportedEncodingException {
365 ctime = -1; // hmm
366 mtime = -1;
367 dev = -1;
368 ino = -1;
369 mode = f.getMode().getBits();
370 uid = -1;
371 gid = -1;
372 try {
373 size = (int) db.openBlob(f.getId()).getSize();
374 } catch (IOException e) {
375 e.printStackTrace();
376 size = -1;
378 sha1 = f.getId();
379 name = f.getFullName().getBytes("UTF-8");
380 flags = (short) ((stage << 12) | name.length); // TODO: fix flags
383 Entry(ByteBuffer b) {
384 int startposition = b.position();
385 ctime = b.getInt() * 1000000000L + (b.getInt() % 1000000000L);
386 mtime = b.getInt() * 1000000000L + (b.getInt() % 1000000000L);
387 dev = b.getInt();
388 ino = b.getInt();
389 mode = b.getInt();
390 uid = b.getInt();
391 gid = b.getInt();
392 size = b.getInt();
393 byte[] sha1bytes = new byte[Constants.OBJECT_ID_LENGTH];
394 b.get(sha1bytes);
395 sha1 = ObjectId.fromRaw(sha1bytes);
396 flags = b.getShort();
397 name = new byte[flags & 0xFFF];
398 b.get(name);
400 .position(startposition
401 + ((8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 20 + 2
402 + name.length + 8) & ~7));
406 * Update this index entry with stat and SHA-1 information if it looks
407 * like the file has been modified in the workdir.
409 * @param f
410 * file in work dir
411 * @return true if a change occurred
412 * @throws IOException
414 public boolean update(File f) throws IOException {
415 boolean modified = false;
416 long lm = f.lastModified() * 1000000L;
417 if (mtime != lm)
418 modified = true;
419 mtime = f.lastModified() * 1000000L;
420 if (size != f.length())
421 modified = true;
422 if (config_filemode()) {
423 if (File_canExecute(f) != FileMode.EXECUTABLE_FILE.equals(mode)) {
424 mode = FileMode.EXECUTABLE_FILE.getBits();
425 modified = true;
428 if (modified) {
429 size = (int) f.length();
430 ObjectWriter writer = new ObjectWriter(db);
431 ObjectId newsha1 = sha1 = writer.writeBlob(f);
432 if (!newsha1.equals(sha1))
433 modified = true;
434 sha1 = newsha1;
436 return modified;
439 void write(ByteBuffer buf) {
440 int startposition = buf.position();
441 buf.putInt((int) (ctime / 1000000000L));
442 buf.putInt((int) (ctime % 1000000000L));
443 buf.putInt((int) (mtime / 1000000000L));
444 buf.putInt((int) (mtime % 1000000000L));
445 buf.putInt(dev);
446 buf.putInt(ino);
447 buf.putInt(mode);
448 buf.putInt(uid);
449 buf.putInt(gid);
450 buf.putInt(size);
451 sha1.copyRawTo(buf);
452 buf.putShort(flags);
453 buf.put(name);
454 int end = startposition
455 + ((8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 20 + 2 + name.length + 8) & ~7);
456 int remain = end - buf.position();
457 while (remain-- > 0)
458 buf.put((byte) 0);
462 * Check if an entry's content is different from the cache,
464 * File status information is used and status is same we
465 * consider the file identical to the state in the working
466 * directory. Native git uses more stat fields than we
467 * have accessible in Java.
469 * @param wd working directory to compare content with
470 * @return true if content is most likely different.
472 public boolean isModified(File wd) {
473 return isModified(wd, false);
477 * Check if an entry's content is different from the cache,
479 * File status information is used and status is same we
480 * consider the file identical to the state in the working
481 * directory. Native git uses more stat fields than we
482 * have accessible in Java.
484 * @param wd working directory to compare content with
485 * @param forceContentCheck True if the actual file content
486 * should be checked if modification time differs.
488 * @return true if content is most likely different.
490 public boolean isModified(File wd, boolean forceContentCheck) {
492 if (isAssumedValid())
493 return false;
495 if (isUpdateNeeded())
496 return true;
498 File file = getFile(wd);
499 if (!file.exists())
500 return true;
502 // JDK1.6 has file.canExecute
503 // if (file.canExecute() != FileMode.EXECUTABLE_FILE.equals(mode))
504 // return true;
505 final int exebits = FileMode.EXECUTABLE_FILE.getBits()
506 ^ FileMode.REGULAR_FILE.getBits();
508 if (config_filemode() && FileMode.EXECUTABLE_FILE.equals(mode)) {
509 if (!File_canExecute(file)&& File_hasExecute())
510 return true;
511 } else {
512 if (FileMode.REGULAR_FILE.equals(mode&~exebits)) {
513 if (!file.isFile())
514 return true;
515 if (config_filemode() && File_canExecute(file) && File_hasExecute())
516 return true;
517 } else {
518 if (FileMode.SYMLINK.equals(mode)) {
519 return true;
520 } else {
521 if (FileMode.TREE.equals(mode)) {
522 if (!file.isDirectory())
523 return true;
524 } else {
525 System.out.println("Does not handle mode "+mode+" ("+file+")");
526 return true;
532 if (file.length() != size)
533 return true;
535 // Git under windows only stores seconds so we round the timestmap
536 // Java gives us if it looks like the timestamp in index is seconds
537 // only. Otherwise we compare the timestamp at millisecond prevision.
538 long javamtime = mtime / 1000000L;
539 long lastm = file.lastModified();
540 if (javamtime % 1000 == 0)
541 lastm = lastm - lastm % 1000;
542 if (lastm != javamtime) {
543 if (!forceContentCheck)
544 return true;
546 try {
547 InputStream is = new FileInputStream(file);
548 ObjectWriter objectWriter = new ObjectWriter(db);
549 try {
550 ObjectId newId = objectWriter.computeBlobSha1(file
551 .length(), is);
552 boolean ret = !newId.equals(sha1);
553 return ret;
554 } catch (IOException e) {
555 e.printStackTrace();
556 } finally {
557 try {
558 is.close();
559 } catch (IOException e) {
560 // can't happen, but if it does we ignore it
561 e.printStackTrace();
564 } catch (FileNotFoundException e) {
565 // should not happen because we already checked this
566 e.printStackTrace();
567 throw new Error(e);
570 return false;
573 // for testing
574 void forceRecheck() {
575 mtime = -1;
578 private File getFile(File wd) {
579 return new File(wd, getName());
582 public String toString() {
583 return new String(name) + "/SHA-1(" + sha1 + ")/M:"
584 + new Date(ctime / 1000000L) + "/C:"
585 + new Date(mtime / 1000000L) + "/d" + dev + "/i" + ino
586 + "/m" + Integer.toString(mode, 8) + "/u" + uid + "/g"
587 + gid + "/s" + size + "/f" + flags + "/@" + getStage();
591 * @return path name for this entry
593 public String getName() {
594 return new String(name);
598 * @return path name for this entry as byte array, hopefully UTF-8 encoded
600 public byte[] getNameUTF8() {
601 return name;
605 * @return SHA-1 of the entry managed by this index
607 public ObjectId getObjectId() {
608 return sha1;
612 * @return the stage this entry is in
614 public int getStage() {
615 return (flags & 0x3000) >> 12;
619 * @return size of disk object
621 public int getSize() {
622 return size;
626 * @return true if this entry shall be assumed valid
628 public boolean isAssumedValid() {
629 return (flags & 0x8000) != 0;
633 * @return true if this entry should be checked for changes
635 public boolean isUpdateNeeded() {
636 return (flags & 0x4000) != 0;
640 * Set whether to always assume this entry valid
642 * @param assumeValid true to ignore changes
644 public void setAssumeValid(boolean assumeValid) {
645 if (assumeValid)
646 flags |= 0x8000;
647 else
648 flags &= ~0x8000;
652 * Set whether this entry must be checked
654 * @param updateNeeded
656 public void setUpdateNeeded(boolean updateNeeded) {
657 if (updateNeeded)
658 flags |= 0x4000;
659 else
660 flags &= ~0x4000;
664 * Return raw file mode bits. See {@link FileMode}
665 * @return file mode bits
667 public int getModeBits() {
668 return mode;
672 static class Header {
673 private int signature;
675 private int version;
677 int entries;
679 Header(ByteBuffer map) throws CorruptObjectException {
680 read(map);
683 private void read(ByteBuffer buf) throws CorruptObjectException {
684 signature = buf.getInt();
685 version = buf.getInt();
686 entries = buf.getInt();
687 if (signature != 0x44495243)
688 throw new CorruptObjectException("Index signature is invalid: "
689 + signature);
690 if (version != 2)
691 throw new CorruptObjectException(
692 "Unknow index version (or corrupt index):" + version);
695 void write(ByteBuffer buf) {
696 buf.order(ByteOrder.BIG_ENDIAN);
697 buf.putInt(signature);
698 buf.putInt(version);
699 buf.putInt(entries);
702 Header(Map entryset) {
703 signature = 0x44495243;
704 version = 2;
705 entries = entryset.size();
709 void readTree(Tree t) throws IOException {
710 readTree("", t);
713 void readTree(String prefix, Tree t) throws IOException {
714 TreeEntry[] members = t.members();
715 for (int i = 0; i < members.length; ++i) {
716 TreeEntry te = members[i];
717 String name;
718 if (prefix.length() > 0)
719 name = prefix + "/" + te.getName();
720 else
721 name = te.getName();
722 if (te instanceof Tree) {
723 readTree(name, (Tree) te);
724 } else {
725 Entry e = new Entry(te, 0);
726 entries.put(name.getBytes("UTF-8"), e);
732 * Add tree entry to index
733 * @param te tree entry
734 * @return new or modified index entry
735 * @throws IOException
737 public Entry addEntry(TreeEntry te) throws IOException {
738 byte[] key = te.getFullName().getBytes("UTF-8");
739 Entry e = new Entry(te, 0);
740 entries.put(key, e);
741 return e;
745 * Check out content of the content represented by the index
747 * @param wd
748 * workdir
749 * @throws IOException
751 public void checkout(File wd) throws IOException {
752 for (Iterator i = entries.values().iterator(); i.hasNext();) {
753 Entry e = (Entry) i.next();
754 if (e.getStage() != 0)
755 continue;
756 checkoutEntry(wd, e);
761 * Check out content of the specified index entry
763 * @param wd workdir
764 * @param e index entry
765 * @throws IOException
767 public void checkoutEntry(File wd, Entry e) throws IOException {
768 ObjectLoader ol = db.openBlob(e.sha1);
769 byte[] bytes = ol.getBytes();
770 File file = new File(wd, e.getName());
771 file.delete();
772 file.getParentFile().mkdirs();
773 FileChannel channel = new FileOutputStream(file).getChannel();
774 ByteBuffer buffer = ByteBuffer.wrap(bytes);
775 int j = channel.write(buffer);
776 if (j != bytes.length)
777 throw new IOException("Could not write file " + file);
778 channel.close();
779 if (config_filemode() && File_hasExecute()) {
780 if (FileMode.EXECUTABLE_FILE.equals(e.mode)) {
781 if (!File_canExecute(file))
782 File_setExecute(file, true);
783 } else {
784 if (File_canExecute(file))
785 File_setExecute(file, false);
788 e.mtime = file.lastModified() * 1000000L;
789 e.ctime = e.mtime;
793 * Construct and write tree out of index.
795 * @return SHA-1 of the constructed tree
797 * @throws IOException
799 public ObjectId writeTree() throws IOException {
800 checkWriteOk();
801 ObjectWriter writer = new ObjectWriter(db);
802 Tree current = new Tree(db);
803 Stack trees = new Stack();
804 trees.push(current);
805 String[] prevName = new String[0];
806 for (Iterator i = entries.values().iterator(); i.hasNext();) {
807 Entry e = (Entry) i.next();
808 if (e.getStage() != 0)
809 continue;
810 String[] newName = splitDirPath(e.getName());
811 int c = longestCommonPath(prevName, newName);
812 while (c < trees.size() - 1) {
813 current.setId(writer.writeTree(current));
814 trees.pop();
815 current = trees.isEmpty() ? null : (Tree) trees.peek();
817 while (trees.size() < newName.length) {
818 if (!current.existsTree(newName[trees.size() - 1])) {
819 current = new Tree(current, newName[trees.size() - 1]
820 .getBytes());
821 current.getParent().addEntry(current);
822 trees.push(current);
823 } else {
824 current = (Tree) current.findTreeMember(newName[trees
825 .size() - 1]);
826 trees.push(current);
829 FileTreeEntry ne = new FileTreeEntry(current, e.sha1,
830 newName[newName.length - 1].getBytes(),
831 (e.mode & FileMode.EXECUTABLE_FILE.getBits()) == FileMode.EXECUTABLE_FILE.getBits());
832 current.addEntry(ne);
834 while (!trees.isEmpty()) {
835 current.setId(writer.writeTree(current));
836 trees.pop();
837 if (!trees.isEmpty())
838 current = (Tree) trees.peek();
840 return current.getTreeId();
843 String[] splitDirPath(String name) {
844 String[] tmp = new String[name.length() / 2 + 1];
845 int p0 = -1;
846 int p1;
847 int c = 0;
848 while ((p1 = name.indexOf('/', p0 + 1)) != -1) {
849 tmp[c++] = name.substring(p0 + 1, p1);
850 p0 = p1;
852 tmp[c++] = name.substring(p0 + 1);
853 String[] ret = new String[c];
854 for (int i = 0; i < c; ++i) {
855 ret[i] = tmp[i];
857 return ret;
860 int longestCommonPath(String[] a, String[] b) {
861 int i;
862 for (i = 0; i < a.length && i < b.length; ++i)
863 if (!a[i].equals(b[i]))
864 return i;
865 return i;
869 * Return the members of the index sorted by the unsigned byte
870 * values of the path names.
872 * Small beware: Unaccounted for are unmerged entries. You may want
873 * to abort if members with stage != 0 are found if you are doing
874 * any updating operations. All stages will be found after one another
875 * here later. Currently only one stage per name is returned.
877 * @return The index entries sorted
879 public Entry[] getMembers() {
880 return (Entry[]) entries.values().toArray(new Entry[entries.size()]);
884 * Look up an entry with the specified path.
886 * @param path
887 * @return index entry for the path or null if not in index.
888 * @throws UnsupportedEncodingException
890 public Entry getEntry(String path) throws UnsupportedEncodingException {
891 return (Entry) entries.get(Repository.gitInternalSlash(path.getBytes("ISO-8859-1")));
895 * @return The repository holding this index.
897 public Repository getRepository() {
898 return db;