From 7bde7cc54a7cdef04f78d3e2c925f8195aff523a Mon Sep 17 00:00:00 2001 From: Robin Rosenberg Date: Thu, 10 May 2007 00:42:23 +0200 Subject: [PATCH] Introduce the Git index This is a partial and non-optimized version of the Git Index. It covers reading and writing of index with stage 0 entries from the index file plus reading from trees plus writing trees. Adding and removing entries as well as refreshing the timestamp and execute bit of the stat information is supported. JDK1.6 is required to support the execute bit though. Most of the stat information is ignored as the Java API does not have any support for things like i-node number, device id or uid/gid information. It is the same index as native Git uses so it should be enough to use native Git, Stacked Git and Eclipse in paralell. Signed-off-by: Robin Rosenberg --- .../src/org/spearce/jgit/lib/FileMode.java | 7 + .../src/org/spearce/jgit/lib/GitIndex.java | 575 +++++++++++++++++++++ .../src/org/spearce/jgit/lib/Tree.java | 2 +- .../org/spearce/jgit/lib/RepositoryTestCase.java | 20 +- .../tst/org/spearce/jgit/lib/T0007_Index.java | 234 +++++++++ .../tst/org/spearce/jgit/lib/empty.gitindex.dat | Bin 0 -> 32 bytes .../tst/org/spearce/jgit/lib/sorttest.gitindex.dat | Bin 0 -> 288 bytes 7 files changed, 835 insertions(+), 3 deletions(-) create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java create mode 100644 org.spearce.jgit/tst/org/spearce/jgit/lib/T0007_Index.java create mode 100644 org.spearce.jgit/tst/org/spearce/jgit/lib/empty.gitindex.dat create mode 100644 org.spearce.jgit/tst/org/spearce/jgit/lib/sorttest.gitindex.dat diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/FileMode.java b/org.spearce.jgit/src/org/spearce/jgit/lib/FileMode.java index fed93681..148231df 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/lib/FileMode.java +++ b/org.spearce.jgit/src/org/spearce/jgit/lib/FileMode.java @@ -108,4 +108,11 @@ public abstract class FileMode { public String toString() { return Integer.toOctalString(modeBits); } + + /** + * @return The mode bits as an integer. + */ + public int getBits() { + return modeBits; + } } diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java new file mode 100644 index 00000000..415a2d7f --- /dev/null +++ b/org.spearce.jgit/src/org/spearce/jgit/lib/GitIndex.java @@ -0,0 +1,575 @@ +package org.spearce.jgit.lib; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; +import java.security.MessageDigest; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; +import java.util.Map; +import java.util.Stack; +import java.util.TreeMap; + +import org.spearce.jgit.errors.CorruptObjectException; + +public class GitIndex { + + private RandomAccessFile cache; + + private File cacheFile; + + // Index is modified + private boolean changed; + + // Stat information updated + private boolean statDirty; + + private Header header; + + private long lastCacheTime; + + private final Repository db; + + private Map entries = new TreeMap(new Comparator() { + public int compare(Object arg0, Object arg1) { + byte[] a = (byte[]) arg0; + byte[] b = (byte[]) arg1; + for (int i = 0; i < a.length && i < b.length; ++i) { + int c = a[i] - b[i]; + if (c != 0) + return c; + } + if (a.length < b.length) + return -1; + else if (a.length > b.length) + return 1; + return 0; + } + }); + + public GitIndex(Repository db) { + this.db = db; + this.cacheFile = new File(db.getDirectory(), "index"); + } + + public boolean isChanged() { + return changed || statDirty; + } + + public void rereadIfNecessary() throws IOException { + if (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) { + read(); + } + } + + public void add(File wd, File f) throws IOException { + byte[] key = Entry.makeKey(wd, f); + Entry e = (Entry) entries.get(key); + if (e == null) { + e = new Entry(key, f, 0, this); + entries.put(key, e); + } else { + e.update(f, db); + } + } + + public void remove(File wd, File f) { + byte[] key = Entry.makeKey(wd, f); + entries.remove(key); + } + + public void read() throws IOException { + long t0 = System.currentTimeMillis(); + changed = false; + statDirty = false; + cache = new RandomAccessFile(cacheFile, "r"); + try { + MappedByteBuffer map = cache.getChannel().map(MapMode.READ_ONLY, 0, + cacheFile.length()); + map.order(ByteOrder.BIG_ENDIAN); + header = new Header(map); + entries.clear(); + for (int i = 0; i < header.entries; ++i) { + Entry entry = new Entry(this, map); + entries.put(entry.name, entry); + } + long t1 = System.currentTimeMillis(); + System.out.println("Read index "+cacheFile+" in "+((t1-t0)/1000.0)+"s"); + } finally { + cache.close(); + } + } + + public void write() throws IOException { + File tmpIndex = new File(cacheFile.getAbsoluteFile() + ".tmp"); + File lock = new File(cacheFile.getAbsoluteFile() + ".lock"); + if (!lock.createNewFile()) + throw new IOException("Index file is in use"); + try { + FileOutputStream fileOutputStream = new FileOutputStream(tmpIndex); + FileChannel fc = fileOutputStream.getChannel(); + ByteBuffer buf = ByteBuffer.allocate(4096); + MessageDigest newMessageDigest = Constants.newMessageDigest(); + header = new Header(entries); + header.write(buf); + buf.flip(); + newMessageDigest + .update(buf.array(), buf.arrayOffset(), buf.limit()); + fc.write(buf); + buf.flip(); + buf.clear(); + for (Iterator i = entries.values().iterator(); i.hasNext();) { + Entry e = (Entry) i.next(); + e.write(buf); + buf.flip(); + newMessageDigest.update(buf.array(), buf.arrayOffset(), buf + .limit()); + fc.write(buf); + buf.flip(); + buf.clear(); + } + buf.put(newMessageDigest.digest()); + buf.flip(); + fc.write(buf); + fc.close(); + fileOutputStream.close(); + if (!tmpIndex.renameTo(cacheFile)) + throw new IOException( + "Could not rename temporary index file to index"); + changed = false; + statDirty = false; + } finally { + if (!lock.delete()) + throw new IOException( + "Could not delete lock file. Should not happen"); + if (tmpIndex.exists() && !tmpIndex.delete()) + throw new IOException( + "Could not delete temporary index file. Should not happen"); + } + } + + public static class Entry { + private long ctime; + + private long mtime; + + private int dev; + + private int ino; + + private int mode; + + private int uid; + + private int gid; + + private int size; + + private ObjectId sha1; + + private short flags; + + private byte[] name; + + private int stage; + + private GitIndex theIndex; + + static byte[] makeKey(File wd, File f) { + if (!f.getPath().startsWith(wd.getPath())) + throw new Error("Path is not in working dir"); + String relName = f.getPath().substring(wd.getPath().length() + 1) + .replace(File.separatorChar, '/'); + return relName.getBytes(); + } + + public Entry(byte[] key, File f, int stage, GitIndex index) + throws IOException { + theIndex = index; + ctime = f.lastModified() * 1000000L; + mtime = ctime; // we use same here + dev = -1; + ino = -1; + mode = FileMode.REGULAR_FILE.getBits(); + uid = -1; + gid = -1; + size = (int) f.length(); + ObjectWriter writer = new ObjectWriter(theIndex.db); + sha1 = writer.writeBlob(f); + name = key; + flags = (short) ((stage << 12) | name.length); // TODO: fix flags + } + + public Entry(TreeEntry f, int stage, GitIndex index) + throws UnsupportedEncodingException { + theIndex = index; + ctime = -1; // hmm + mtime = -1; + dev = -1; + ino = -1; + mode = f.getMode().getBits(); + uid = -1; + gid = -1; + size = -1; + sha1 = f.getId(); + name = f.getFullName().getBytes("UTF-8"); + flags = (short) ((stage << 12) | name.length); // TODO: fix flags + } + + Entry(GitIndex index, ByteBuffer b) { + theIndex = index; + int startposition = b.position(); + ctime = b.getInt() * 1000000000L + (b.getInt() % 1000000000L); + mtime = b.getInt() * 1000000000L + (b.getInt() % 1000000000L); + dev = b.getInt(); + ino = b.getInt(); + mode = b.getInt(); + uid = b.getInt(); + gid = b.getInt(); + size = b.getInt(); + byte[] sha1bytes = new byte[Constants.OBJECT_ID_LENGTH]; + b.get(sha1bytes); + sha1 = new ObjectId(sha1bytes); + flags = b.getShort(); + stage = (flags & 0x4000) >> 12; + name = new byte[flags & 0xFFF]; + b.get(name); + b + .position(startposition + + ((8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 20 + 2 + + name.length + 8) & ~7)); + } + + public boolean update(File f, Repository db) throws IOException { + boolean modified = false; + if (mtime != f.lastModified()) + modified = true; + mtime = f.lastModified() * 1000000L; + if (size != f.length()) + modified = true; + size = (int) f.length(); + ObjectWriter writer = new ObjectWriter(db); + ObjectId newsha1 = sha1 = writer.writeBlob(f); + if (!newsha1.equals(sha1)) + modified = true; + sha1 = newsha1; + return modified; + } + + public void write(ByteBuffer buf) { + int startposition = buf.position(); + buf.putInt((int) (ctime / 1000000000L)); + buf.putInt((int) (ctime % 1000000000L)); + buf.putInt((int) (mtime / 1000000000L)); + buf.putInt((int) (mtime % 1000000000L)); + buf.putInt(dev); + buf.putInt(ino); + buf.putInt(mode); + buf.putInt(uid); + buf.putInt(gid); + buf.putInt(size); + buf.put(sha1.getBytes()); + buf.putShort(flags); + buf.put(name); + int end = startposition + + ((8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 20 + 2 + name.length + 8) & ~7); + int remain = end - buf.position(); + while (remain-- > 0) + buf.put((byte) 0); + } + + static Method canExecute; + static { + try { + canExecute = File.class.getMethod("canExecute", (Class[]) null); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /* + * JDK1.6 has file.canExecute + * + * if (file.canExecute() != FileMode.EXECUTABLE_FILE.equals(mode)) + * return true; + */ + boolean File_canExecute(File f) { + if (canExecute != null) { + try { + return ((Boolean) canExecute.invoke(f, (Object[]) null)) + .booleanValue(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } + } else + return false; + } + + public boolean isModified(File wd) { + File file = getFile(wd); + if (!file.exists()) + return true; + + // JDK1.6 has file.canExecute + // if (file.canExecute() != FileMode.EXECUTABLE_FILE.equals(mode)) + // return true; + if (FileMode.EXECUTABLE_FILE.equals(mode)) { + if (!File_canExecute(file)) + return true; + } else { + if (FileMode.REGULAR_FILE.equals(mode)) { + if (!file.isFile()) + return true; + if (File_canExecute(file)) + return true; + } else { + if (FileMode.SYMLINK.equals(mode)) { + return true; + } else { + if (FileMode.TREE.equals(mode)) { + if (!file.isDirectory()) + return true; + } else { + System.out.println("Does not handle mode "+mode+" ("+file+")"); + return true; + } + } + } + } + + long javamtime = mtime / 1000000L; + long lastm = file.lastModified(); + if (file.length() != size) + return true; + if (lastm != javamtime) { + try { + InputStream is = new FileInputStream(file); + ObjectWriter objectWriter = new ObjectWriter(theIndex.db); + try { + ObjectId newId = objectWriter.computeBlobSha1(file + .length(), is); + boolean ret = newId.equals(sha1); + theIndex.statDirty = true; + return ret; + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + is.close(); + } catch (IOException e) { + // can't happen, but if it does we ignore it + e.printStackTrace(); + } + } + } catch (FileNotFoundException e) { + // should not happen because we already checked this + e.printStackTrace(); + throw new Error(e); + } + } + return false; + } + + private File getFile(File wd) { + return new File(wd, getName()); + } + + public String toString() { + return new String(name) + "/SHA-1(" + sha1 + ")/M:" + + new Date(ctime / 1000000L) + "/C:" + + new Date(mtime / 1000000L) + "/d" + dev + "/i" + ino + + "/m" + Integer.toString(mode, 8) + "/u" + uid + "/g" + + gid + "/s" + size + "/f" + flags + "/@" + stage; + } + + public String getName() { + return new String(name); + } + + public ObjectId getObjectId() { + return sha1; + } + } + + static class Header { + private int signature; + + private int version; + + int entries; + + public Header(ByteBuffer map) throws CorruptObjectException { + read(map); + } + + private void read(ByteBuffer buf) throws CorruptObjectException { + signature = buf.getInt(); + version = buf.getInt(); + entries = buf.getInt(); + if (signature != 0x44495243) + throw new CorruptObjectException("Index signature is invalid: " + + signature); + if (version != 2) + throw new CorruptObjectException( + "Unknow index version (or corrupt index):" + version); + } + + public void write(ByteBuffer buf) { + buf.order(ByteOrder.BIG_ENDIAN); + buf.putInt(signature); + buf.putInt(version); + buf.putInt(entries); + } + + public Header(Map entryset) { + signature = 0x44495243; + version = 2; + entries = entryset.size(); + } + } + + public void readTree(Tree t) throws IOException { + readTree("", t); + } + + public void readTree(String prefix, Tree t) throws IOException { + TreeEntry[] members = t.members(); + for (int i = 0; i < members.length; ++i) { + TreeEntry te = members[i]; + String name; + if (prefix.length() > 0) + name = prefix + "/" + te.getName(); + else + name = te.getName(); + if (te instanceof Tree) { + readTree(name, (Tree) te); + } else { + Entry e = new Entry(te, 0, this); + entries.put(name.getBytes("UTF-8"), e); + } + } + } + + public void checkout(File wd) throws IOException { + for (Iterator i = entries.values().iterator(); i.hasNext();) { + Entry e = (Entry) i.next(); + if (e.stage != 0) + continue; + ObjectLoader ol = db.openBlob(e.sha1); + byte[] bytes = ol.getBytes(); + File file = new File(wd, e.getName()); + file.delete(); + file.getParentFile().mkdirs(); + FileChannel channel = new FileOutputStream(file).getChannel(); + ByteBuffer buffer = ByteBuffer.wrap(bytes); + int j = channel.write(buffer); + if (j != bytes.length) + throw new IOException("Could not write file " + file); + channel.close(); + } + } + + public ObjectId writeTree(File wd) throws IOException { + ObjectWriter writer = new ObjectWriter(db); + Tree current = new Tree(db); + Stack trees = new Stack(); + trees.push(current); + String[] prevName = new String[0]; + for (Iterator i = entries.values().iterator(); i.hasNext();) { + Entry e = (Entry) i.next(); + if (e.stage != 0) + continue; + String[] newName = splitDirPath(e.getName()); + int c = longestCommonPath(prevName, newName); + while (c < trees.size() - 1) { + current.setId(writer.writeTree(current)); + trees.pop(); + current = trees.isEmpty() ? null : (Tree) trees.peek(); + } + while (trees.size() < newName.length) { + if (!current.existsTree(newName[trees.size() - 1])) { + current = new Tree(current, newName[trees.size() - 1] + .getBytes()); + current.getParent().addEntry(current); + trees.push(current); + } else { + current = (Tree) current.findTreeMember(newName[trees + .size() - 1]); + trees.push(current); + } + } + FileTreeEntry ne = new FileTreeEntry(current, e.sha1, + newName[newName.length - 1].getBytes(), + (e.mode & FileMode.EXECUTABLE_FILE.getBits()) == FileMode.EXECUTABLE_FILE.getBits()); + current.addEntry(ne); + } + while (!trees.isEmpty()) { + current.setId(writer.writeTree(current)); + trees.pop(); + if (!trees.isEmpty()) + current = (Tree) trees.peek(); + } + return current.getTreeId(); + } + + String[] splitDirPath(String name) { + String[] tmp = new String[name.length() / 2 + 1]; + int p0 = -1; + int p1; + int c = 0; + while ((p1 = name.indexOf('/', p0 + 1)) != -1) { + tmp[c++] = name.substring(p0 + 1, p1); + p0 = p1; + } + tmp[c++] = name.substring(p0 + 1); + String[] ret = new String[c]; + for (int i = 0; i < c; ++i) { + ret[i] = tmp[i]; + } + return ret; + } + + int longestCommonPath(String[] a, String[] b) { + int i; + for (i = 0; i < a.length && i < b.length; ++i) + if (!a[i].equals(b[i])) + return i; + return i; + } + + public Entry[] getMembers() { + return (Entry[]) entries.values().toArray(new Entry[entries.size()]); + } + + public Entry getEntry(String path) throws UnsupportedEncodingException { + return (Entry) entries.get(path.getBytes("ISO-8859-1")); + } + +} diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Tree.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Tree.java index 73a5e2cf..550906d3 100644 --- a/org.spearce.jgit/src/org/spearce/jgit/lib/Tree.java +++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Tree.java @@ -118,7 +118,7 @@ public class Tree extends TreeEntry implements Treeish { readTree(raw); } - private Tree(final Tree parent, final byte[] nameUTF8) { + public Tree(final Tree parent, final byte[] nameUTF8) { super(parent, null, nameUTF8); db = parent.getRepository(); contents = EMPTY_TREE; diff --git a/org.spearce.jgit/tst/org/spearce/jgit/lib/RepositoryTestCase.java b/org.spearce.jgit/tst/org/spearce/jgit/lib/RepositoryTestCase.java index 2d5591e2..7d4052e6 100644 --- a/org.spearce.jgit/tst/org/spearce/jgit/lib/RepositoryTestCase.java +++ b/org.spearce.jgit/tst/org/spearce/jgit/lib/RepositoryTestCase.java @@ -21,6 +21,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; +import java.util.Arrays; import junit.framework.TestCase; @@ -69,12 +70,27 @@ public abstract class RepositoryTestCase extends TestCase { fos.close(); } - protected static void writeTrashFile(final String name, final String data) + protected static File writeTrashFile(final String name, final String data) throws IOException { + File tf = new File(trash, name); + File tfp = tf.getParentFile(); + if (!tfp.exists() && !tf.getParentFile().mkdirs()) + throw new Error("Could not create directory " + tf.getParentFile()); final OutputStreamWriter fw = new OutputStreamWriter( - new FileOutputStream(new File(trash, name)), "UTF-8"); + new FileOutputStream(tf), "UTF-8"); fw.write(data); fw.close(); + return tf; + } + + protected static void checkFile(File f, final String checkData) + throws IOException { + byte[] data = new byte[(int) f.length()]; + assertEquals(f.length(), data.length); + FileInputStream stream = new FileInputStream(f); + stream.read(data); + byte[] bytes = checkData.getBytes("ISO-8859-1"); + assertTrue(Arrays.equals(bytes, data)); } protected Repository db; diff --git a/org.spearce.jgit/tst/org/spearce/jgit/lib/T0007_Index.java b/org.spearce.jgit/tst/org/spearce/jgit/lib/T0007_Index.java new file mode 100644 index 00000000..d1d97c42 --- /dev/null +++ b/org.spearce.jgit/tst/org/spearce/jgit/lib/T0007_Index.java @@ -0,0 +1,234 @@ +package org.spearce.jgit.lib; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.spearce.jgit.lib.GitIndex.Entry; + +public class T0007_Index extends RepositoryTestCase { + + private int system(File dir, String cmd) throws IOException, + InterruptedException { + final Process process = Runtime.getRuntime().exec(cmd, null, dir); + new Thread() { + public void run() { + try { + InputStream s = process.getErrorStream(); + for (int c = s.read(); c != -1; c = s.read()) { + System.err.print((char) c); + } + s.close(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + }.start(); + final Thread t2 = new Thread() { + public void run() { + synchronized (this) { + try { + InputStream e = process.getInputStream(); + for (int c = e.read(); c != -1; c = e.read()) { + System.out.print((char) c); + } + e.close(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + } + }; + t2.start(); + process.getOutputStream().close(); + int ret = process.waitFor(); + synchronized (t2) { + return ret; + } + } + + public void testCreateEmptyIndex() throws Exception { + Repository db = new Repository(trash_git); + GitIndex index = new GitIndex(db); + index.write(); +// native git doesn't like an empty index +// assertEquals(0,system(trash,"git status")); + } + + public void testCreateSimpleSortTestIndex() throws Exception { + Repository db = new Repository(trash_git); + GitIndex index = new GitIndex(db); + writeTrashFile("a/b", "data:a/b"); + writeTrashFile("a:b", "data:a:b"); + writeTrashFile("a.b", "data:a.b"); + index.add(trash, new File(trash, "a/b")); + index.add(trash, new File(trash, "a:b")); + index.add(trash, new File(trash, "a.b")); + index.write(); + assertEquals(0, system(trash, "git status")); + + assertEquals("a/b", index.getEntry("a/b").getName()); + assertEquals("a:b", index.getEntry("a:b").getName()); + assertEquals("a.b", index.getEntry("a.b").getName()); + assertNull(index.getEntry("a*b")); + } + + public void testUpdateSimpleSortTestIndex() throws Exception { + Repository db = new Repository(trash_git); + GitIndex index = new GitIndex(db); + writeTrashFile("a/b", "data:a/b"); + writeTrashFile("a:b", "data:a:b"); + writeTrashFile("a.b", "data:a.b"); + index.add(trash, new File(trash, "a/b")); + index.add(trash, new File(trash, "a:b")); + index.add(trash, new File(trash, "a.b")); + writeTrashFile("a/b", "data:a/b modified"); + index.add(trash, new File(trash, "a/b")); + index.write(); + assertEquals(0, system(trash, "git status")); + } + + public void testWriteTree() throws Exception { + Repository db = new Repository(trash_git); + GitIndex index = new GitIndex(db); + writeTrashFile("a/b", "data:a/b"); + writeTrashFile("a:b", "data:a:b"); + writeTrashFile("a.b", "data:a.b"); + index.add(trash, new File(trash, "a/b")); + index.add(trash, new File(trash, "a:b")); + index.add(trash, new File(trash, "a.b")); + index.write(); + assertEquals(0, system(trash, "git status")); + ObjectId id = index.writeTree(trash); + assertEquals("c696abc3ab8e091c665f49d00eb8919690b3aec3", id.toString()); + + writeTrashFile("a/b", "data:a/b"); + index.add(trash, new File(trash, "a/b")); + } + + public void testReadTree() throws Exception { + // Prepare tree + Repository db = new Repository(trash_git); + GitIndex index = new GitIndex(db); + writeTrashFile("a/b", "data:a/b"); + writeTrashFile("a:b", "data:a:b"); + writeTrashFile("a.b", "data:a.b"); + index.add(trash, new File(trash, "a/b")); + index.add(trash, new File(trash, "a:b")); + index.add(trash, new File(trash, "a.b")); + index.write(); + assertEquals(0, system(trash, "git status")); + ObjectId id = index.writeTree(trash); + System.out.println("wrote id " + id); + assertEquals("c696abc3ab8e091c665f49d00eb8919690b3aec3", id.toString()); + GitIndex index2 = new GitIndex(db); + + index2.readTree(db.mapTree(new ObjectId( + "c696abc3ab8e091c665f49d00eb8919690b3aec3"))); + Entry[] members = index2.getMembers(); + assertEquals(3, members.length); + assertEquals("a.b", members[0].getName()); + assertEquals("a/b", members[1].getName()); + assertEquals("a:b", members[2].getName()); + assertEquals(3, members.length); + + } + + public void testReadTree2() throws Exception { + // Prepare a larger tree to test some odd cases in tree writing + Repository db = new Repository(trash_git); + GitIndex index = new GitIndex(db); + File f1 = writeTrashFile("a/a/a/a", "data:a/a/a/a"); + File f2 = writeTrashFile("a/c/c", "data:a/c/c"); + File f3 = writeTrashFile("a/b", "data:a/b"); + File f4 = writeTrashFile("a:b", "data:a:b"); + File f5 = writeTrashFile("a/d", "data:a/d"); + File f6 = writeTrashFile("a.b", "data:a.b"); + index.add(trash, f1); + index.add(trash, f2); + index.add(trash, f3); + index.add(trash, f4); + index.add(trash, f5); + index.add(trash, f6); + index.write(); + ObjectId id = index.writeTree(trash); + System.out.println("wrote id " + id); + assertEquals("ba78e065e2c261d4f7b8f42107588051e87e18e9", id.toString()); + GitIndex index2 = new GitIndex(db); + + index2.readTree(db.mapTree(new ObjectId( + "ba78e065e2c261d4f7b8f42107588051e87e18e9"))); + Entry[] members = index2.getMembers(); + assertEquals(6, members.length); + assertEquals("a.b", members[0].getName()); + assertEquals("a/a/a/a", members[1].getName()); + assertEquals("a/b", members[2].getName()); + assertEquals("a/c/c", members[3].getName()); + assertEquals("a/d", members[4].getName()); + assertEquals("a:b", members[5].getName()); + } + + public void testDelete() throws Exception { + Repository db = new Repository(trash_git); + GitIndex index = new GitIndex(db); + writeTrashFile("a/b", "data:a/b"); + writeTrashFile("a:b", "data:a:b"); + writeTrashFile("a.b", "data:a.b"); + index.add(trash, new File(trash, "a/b")); + index.add(trash, new File(trash, "a:b")); + index.add(trash, new File(trash, "a.b")); + index.write(); + assertEquals(0, system(trash, "git status")); + index.writeTree(trash); + index.remove(trash, new File(trash, "a:b")); + index.write(); + assertEquals("a.b", index.getMembers()[0].getName()); + assertEquals("a/b", index.getMembers()[1].getName()); + } + + public void testCheckout() throws Exception { + // Prepare tree, remote it and checkout + Repository db = new Repository(trash_git); + GitIndex index = new GitIndex(db); + File aslashb = writeTrashFile("a/b", "data:a/b"); + File acolonb = writeTrashFile("a:b", "data:a:b"); + File adotb = writeTrashFile("a.b", "data:a.b"); + index.add(trash, aslashb); + index.add(trash, acolonb); + index.add(trash, adotb); + index.write(); + assertEquals(0, system(trash, "git status")); + index.writeTree(trash); + delete(aslashb); + delete(acolonb); + delete(adotb); + delete(aslashb.getParentFile()); + + GitIndex index2 = new GitIndex(db); + assertEquals(0, index2.getMembers().length); + + index2.readTree(db.mapTree(new ObjectId( + "c696abc3ab8e091c665f49d00eb8919690b3aec3"))); + + index2.checkout(trash); + assertEquals("data:a/b", content(aslashb)); + assertEquals("data:a:b", content(acolonb)); + assertEquals("data:a.b", content(adotb)); + } + + private String content(File f) throws IOException { + byte[] buf = new byte[(int) f.length()]; + FileInputStream is = new FileInputStream(f); + int read = is.read(buf); + assertEquals(f.length(), read); + return new String(buf, 0); + } + + private void delete(File f) throws IOException { + if (!f.delete()) + throw new IOException("Failed to delete f"); + } +} diff --git a/org.spearce.jgit/tst/org/spearce/jgit/lib/empty.gitindex.dat b/org.spearce.jgit/tst/org/spearce/jgit/lib/empty.gitindex.dat new file mode 100644 index 0000000000000000000000000000000000000000..3330d716f1541c5acb1ed77867020cc7d88df5b7 GIT binary patch literal 32 mcwTey402{*U|<4b2Fn{0gy%gq&8hoZq?q&itoka)pZ5Wd4h#_h literal 0 HcwPel00001 diff --git a/org.spearce.jgit/tst/org/spearce/jgit/lib/sorttest.gitindex.dat b/org.spearce.jgit/tst/org/spearce/jgit/lib/sorttest.gitindex.dat new file mode 100644 index 0000000000000000000000000000000000000000..217f2e3811eac53b751d82131ac3700bce0283ff GIT binary patch literal 288 zcwTey402{*U|<4bX4hL9$AB~gjAmfqVCPcXI*EayaR~zh;}-~KV0bq7(dt<8 literal 0 HcwPel00001 -- 2.11.4.GIT