/*
* Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
* Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
* Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.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.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.text.MessageFormat;
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.eclipse.jgit.JGitText;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.events.IndexChangedEvent;
import org.eclipse.jgit.util.RawParseUtils;
/**
* A representation of the Git index.
*
* The index points to the objects currently checked out or in the process of
* being prepared for committing or objects involved in an unfinished merge.
*
* The abstract format is:<br/> path stage flags statdata SHA-1
* <ul>
* <li>Path is the relative path in the workdir</li>
* <li>stage is 0 (normally), but when
* merging 1 is the common ancestor version, 2 is 'our' version and 3 is 'their'
* version. A fully resolved merge only contains stage 0.</li>
* <li>flags is the object type and information of validity</li>
* <li>statdata is the size of this object and some other file system specifics,
* some of it ignored by JGit</li>
* <li>SHA-1 represents the content of the references object</li>
* </ul>
*
* An index can also contain a tree cache which we ignore for now. We drop the
* tree cache when writing the index.
*
* @deprecated Use {@link DirCache} instead.
*/
public class GitIndex {
/** Stage 0 represents merged entries. */
public static final int STAGE_0 = 0;
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<byte[], Entry> entries = new TreeMap<byte[], Entry>(new Comparator<byte[]>() {
public int compare(byte[] o1, byte[] o2) {
for (int i = 0; i < o1.length && i < o2.length; ++i) {
int c = (o1[i] & 0xff) - (o2[i] & 0xff);
if (c != 0)
return c;
}
if (o1.length < o2.length)
return -1;
else if (o1.length > o2.length)
return 1;
return 0;
}
});
/**
* Construct a Git index representation.
* @param db
*/
public GitIndex(Repository db) {
this.db = db;
this.cacheFile = db.getIndexFile();
}
/**
* @return true if we have modified the index in memory since reading it from disk
*/
public boolean isChanged() {
return changed || statDirty;
}
/**
* Reread index data from disk if the index file has been changed
* @throws IOException
*/
public void rereadIfNecessary() throws IOException {
if (cacheFile.exists() && cacheFile.lastModified() != lastCacheTime) {
read();
db.fireEvent(new IndexChangedEvent());
}
}
/**
* Add the content of a file to the index.
*
* @param wd workdir
* @param f the file
* @return a new or updated index entry for the path represented by f
* @throws IOException
*/
public Entry add(File wd, File f) throws IOException {
byte[] key = makeKey(wd, f);
Entry e = entries.get(key);
if (e == null) {
e = new Entry(key, f, 0);
entries.put(key, e);
} else {
e.update(f);
}
return e;
}
/**
* Add the content of a file to the index.
*
* @param wd
* workdir
* @param f
* the file
* @param content
* content of the file
* @return a new or updated index entry for the path represented by f
* @throws IOException
*/
public Entry add(File wd, File f, byte[] content) throws IOException {
byte[] key = makeKey(wd, f);
Entry e = entries.get(key);
if (e == null) {
e = new Entry(key, f, 0, content);
entries.put(key, e);
} else {
e.update(f, content);
}
return e;
}
/**
* Remove a path from the index.
*
* @param wd
* workdir
* @param f
* the file whose path shall be removed.
* @return true if such a path was found (and thus removed)
* @throws IOException
*/
public boolean remove(File wd, File f) throws IOException {
byte[] key = makeKey(wd, f);
return entries.remove(key) != null;
}
/**
* Read the cache file into memory.
*
* @throws IOException
*/
public void read() throws IOException {
changed = false;
statDirty = false;
if (!cacheFile.exists()) {
header = null;
entries.clear();
lastCacheTime = 0;
return;
}
cache = new RandomAccessFile(cacheFile, "r");
try {
FileChannel channel = cache.getChannel();
ByteBuffer buffer = ByteBuffer.allocateDirect((int) cacheFile.length());
buffer.order(ByteOrder.BIG_ENDIAN);
int j = channel.read(buffer);
if (j != buffer.capacity())
throw new IOException(MessageFormat.format(JGitText.get().couldNotReadIndexInOneGo
, j, buffer.capacity()));
buffer.flip();
header = new Header(buffer);
entries.clear();
for (int i = 0; i < header.entries; ++i) {
Entry entry = new Entry(buffer);
final GitIndex.Entry existing = entries.get(entry.name);
entries.put(entry.name, entry);
if (existing != null) {
entry.stages |= existing.stages;
}
}
lastCacheTime = cacheFile.lastModified();
} finally {
cache.close();
}
}
/**
* Write content of index to disk.
*
* @throws IOException
*/
public void write() throws IOException {
checkWriteOk();
File tmpIndex = new File(cacheFile.getAbsoluteFile() + ".tmp");
File lock = new File(cacheFile.getAbsoluteFile() + ".lock");
if (!lock.createNewFile())
throw new IOException(JGitText.get().indexFileIsInUse);
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 (cacheFile.exists()) {
if (db.getFS().retryFailedLockFileCommit()) {
// file deletion fails on windows if another
// thread is reading the file concurrently
// So let's try 10 times...
boolean deleted = false;
for (int i = 0; i < 10; i++) {
if (cacheFile.delete()) {
deleted = true;
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// ignore
}
}
if (!deleted)
throw new IOException(
JGitText.get().couldNotRenameDeleteOldIndex);
} else {
if (!cacheFile.delete())
throw new IOException(
JGitText.get().couldNotRenameDeleteOldIndex);
}
}
if (!tmpIndex.renameTo(cacheFile))
throw new IOException(
JGitText.get().couldNotRenameTemporaryIndexFileToIndex);
changed = false;
statDirty = false;
lastCacheTime = cacheFile.lastModified();
db.fireEvent(new IndexChangedEvent());
} finally {
if (!lock.delete())
throw new IOException(
JGitText.get().couldNotDeleteLockFileShouldNotHappen);
if (tmpIndex.exists() && !tmpIndex.delete())
throw new IOException(
JGitText.get().couldNotDeleteTemporaryIndexFileShouldNotHappen);
}
}
private void checkWriteOk() throws IOException {
for (Iterator i = entries.values().iterator(); i.hasNext();) {
Entry e = (Entry) i.next();
if (e.getStage() != 0) {
throw new NotSupportedException(JGitText.get().cannotWorkWithOtherStagesThanZeroRightNow);
}
}
}
private boolean File_canExecute(File f){
return db.getFS().canExecute(f);
}
private boolean File_setExecute(File f, boolean value) {
return db.getFS().setExecute(f, value);
}
private boolean File_hasExecute() {
return db.getFS().supportsExecute();
}
static byte[] makeKey(File wd, File f) {
if (!f.getPath().startsWith(wd.getPath()))
throw new Error(JGitText.get().pathIsNotInWorkingDir);
String relName = Repository.stripWorkDir(wd, f);
return Constants.encode(relName);
}
Boolean filemode;
private boolean config_filemode() {
// temporary til we can actually set parameters. We need to be able
// to change this for testing.
if (filemode != null)
return filemode.booleanValue();
Config config = db.getConfig();
filemode = Boolean.valueOf(config.getBoolean("core", null, "filemode", true));
return filemode.booleanValue();
}
/**
* An index entry
*
* @deprecated Use {@link org.eclipse.jgit.dircache.DirCacheEntry}.
*/
@Deprecated
public 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 stages;
Entry(byte[] key, File f, int stage)
throws IOException {
ctime = f.lastModified() * 1000000L;
mtime = ctime; // we use same here
dev = -1;
ino = -1;
if (config_filemode() && File_canExecute(f))
mode = FileMode.EXECUTABLE_FILE.getBits();
else
mode = FileMode.REGULAR_FILE.getBits();
uid = -1;
gid = -1;
size = (int) f.length();
ObjectWriter writer = new ObjectWriter(db);
sha1 = writer.writeBlob(f);
name = key;
flags = (short) ((stage << 12) | name.length); // TODO: fix flags
stages = (1 >> getStage());
}
Entry(byte[] key, File f, int stage, byte[] newContent)
throws IOException {
ctime = f.lastModified() * 1000000L;
mtime = ctime; // we use same here
dev = -1;
ino = -1;
if (config_filemode() && File_canExecute(f))
mode = FileMode.EXECUTABLE_FILE.getBits();
else
mode = FileMode.REGULAR_FILE.getBits();
uid = -1;
gid = -1;
size = newContent.length;
ObjectWriter writer = new ObjectWriter(db);
sha1 = writer.writeBlob(newContent);
name = key;
flags = (short) ((stage << 12) | name.length); // TODO: fix flags
stages = (1 >> getStage());
}
Entry(TreeEntry f, int stage) {
ctime = -1; // hmm
mtime = -1;
dev = -1;
ino = -1;
mode = f.getMode().getBits();
uid = -1;
gid = -1;
try {
size = (int) db.open(f.getId(), Constants.OBJ_BLOB).getSize();
} catch (IOException e) {
e.printStackTrace();
size = -1;
}
sha1 = f.getId();
name = Constants.encode(f.getFullName());
flags = (short) ((stage << 12) | name.length); // TODO: fix flags
stages = (1 >> getStage());
}
Entry(ByteBuffer b) {
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 = ObjectId.fromRaw(sha1bytes);
flags = b.getShort();
stages = (1 << getStage());
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));
}
/**
* Update this index entry with stat and SHA-1 information if it looks
* like the file has been modified in the workdir.
*
* @param f
* file in work dir
* @return true if a change occurred
* @throws IOException
*/
public boolean update(File f) throws IOException {
long lm = f.lastModified() * 1000000L;
boolean modified = mtime != lm;
mtime = lm;
if (size != f.length())
modified = true;
if (config_filemode()) {
if (File_canExecute(f) != FileMode.EXECUTABLE_FILE.equals(mode)) {
mode = FileMode.EXECUTABLE_FILE.getBits();
modified = true;
}
}
if (modified) {
size = (int) f.length();
ObjectWriter writer = new ObjectWriter(db);
ObjectId newsha1 = writer.writeBlob(f);
if (!newsha1.equals(sha1))
modified = true;
sha1 = newsha1;
}
return modified;
}
/**
* Update this index entry with stat and SHA-1 information if it looks
* like the file has been modified in the workdir.
*
* @param f
* file in work dir
* @param newContent
* the new content of the file
* @return true if a change occurred
* @throws IOException
*/
public boolean update(File f, byte[] newContent) throws IOException {
boolean modified = false;
size = newContent.length;
ObjectWriter writer = new ObjectWriter(db);
ObjectId newsha1 = writer.writeBlob(newContent);
if (!newsha1.equals(sha1))
modified = true;
sha1 = newsha1;
return modified;
}
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);
sha1.copyRawTo(buf);
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);
}
/**
* Check if an entry's content is different from the cache,
*
* File status information is used and status is same we
* consider the file identical to the state in the working
* directory. Native git uses more stat fields than we
* have accessible in Java.
*
* @param wd working directory to compare content with
* @return true if content is most likely different.
*/
public boolean isModified(File wd) {
return isModified(wd, false);
}
/**
* Check if an entry's content is different from the cache,
*
* File status information is used and status is same we
* consider the file identical to the state in the working
* directory. Native git uses more stat fields than we
* have accessible in Java.
*
* @param wd working directory to compare content with
* @param forceContentCheck True if the actual file content
* should be checked if modification time differs.
*
* @return true if content is most likely different.
*/
public boolean isModified(File wd, boolean forceContentCheck) {
if (isAssumedValid())
return false;
if (isUpdateNeeded())
return true;
File file = getFile(wd);
long length = file.length();
if (length == 0) {
if (!file.exists())
return true;
}
if (length != size)
return true;
// JDK1.6 has file.canExecute
// if (file.canExecute() != FileMode.EXECUTABLE_FILE.equals(mode))
// return true;
final int exebits = FileMode.EXECUTABLE_FILE.getBits()
^ FileMode.REGULAR_FILE.getBits();
if (config_filemode() && FileMode.EXECUTABLE_FILE.equals(mode)) {
if (!File_canExecute(file)&& File_hasExecute())
return true;
} else {
if (FileMode.REGULAR_FILE.equals(mode&~exebits)) {
if (!file.isFile())
return true;
if (config_filemode() && File_canExecute(file) && File_hasExecute())
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(MessageFormat.format(JGitText.get().doesNotHandleMode
, mode, file));
return true;
}
}
}
}
// Git under windows only stores seconds so we round the timestamp
// Java gives us if it looks like the timestamp in index is seconds
// only. Otherwise we compare the timestamp at millisecond prevision.
long javamtime = mtime / 1000000L;
long lastm = file.lastModified();
if (javamtime % 1000 == 0)
lastm = lastm - lastm % 1000;
if (lastm != javamtime) {
if (!forceContentCheck)
return true;
try {
InputStream is = new FileInputStream(file);
try {
ObjectWriter objectWriter = new ObjectWriter(db);
ObjectId newId = objectWriter.computeBlobSha1(file
.length(), is);
boolean ret = !newId.equals(sha1);
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;
}
/**
* Returns the stages in which the entry's file is recorded in the index.
* The stages are bit-encoded: bit N is set if the file is present
* in stage N. In particular, the N-th bit will be set if this entry
* itself is in stage N (see getStage()).
*
* @return flags denoting stages
* @see #getStage()
*/
public int getStages() {
return stages;
}
// for testing
void forceRecheck() {
mtime = -1;
}
private File getFile(File wd) {
return new File(wd, getName());
}
public String toString() {
return getName() + "/SHA-1(" + sha1.name() + ")/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 + "/@" + getStage();
}
/**
* @return path name for this entry
*/
public String getName() {
return RawParseUtils.decode(name);
}
/**
* @return path name for this entry as byte array, hopefully UTF-8 encoded
*/
public byte[] getNameUTF8() {
return name;
}
/**
* @return SHA-1 of the entry managed by this index
*/
public ObjectId getObjectId() {
return sha1;
}
/**
* @return the stage this entry is in
*/
public int getStage() {
return (flags & 0x3000) >> 12;
}
/**
* @return size of disk object
*/
public int getSize() {
return size;
}
/**
* @return true if this entry shall be assumed valid
*/
public boolean isAssumedValid() {
return (flags & 0x8000) != 0;
}
/**
* @return true if this entry should be checked for changes
*/
public boolean isUpdateNeeded() {
return (flags & 0x4000) != 0;
}
/**
* Set whether to always assume this entry valid
*
* @param assumeValid true to ignore changes
*/
public void setAssumeValid(boolean assumeValid) {
if (assumeValid)
flags |= 0x8000;
else
flags &= ~0x8000;
}
/**
* Set whether this entry must be checked
*
* @param updateNeeded
*/
public void setUpdateNeeded(boolean updateNeeded) {
if (updateNeeded)
flags |= 0x4000;
else
flags &= ~0x4000;
}
/**
* Return raw file mode bits. See {@link FileMode}
* @return file mode bits
*/
public int getModeBits() {
return mode;
}
}
static class Header {
private int signature;
private int version;
int entries;
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(MessageFormat.format(
JGitText.get().indexSignatureIsInvalid, signature));
if (version != 2)
throw new CorruptObjectException(MessageFormat.format(
JGitText.get().unknownIndexVersionOrCorruptIndex, version));
}
void write(ByteBuffer buf) {
buf.order(ByteOrder.BIG_ENDIAN);
buf.putInt(signature);
buf.putInt(version);
buf.putInt(entries);
}
Header(Map entryset) {
signature = 0x44495243;
version = 2;
entries = entryset.size();
}
}
/**
* Read a Tree recursively into the index
*
* @param t The tree to read
*
* @throws IOException
*/
public void readTree(Tree t) throws IOException {
entries.clear();
readTree("", t);
}
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);
entries.put(Constants.encode(name), e);
}
}
}
/**
* Add tree entry to index
* @param te tree entry
* @return new or modified index entry
* @throws IOException
*/
public Entry addEntry(TreeEntry te) throws IOException {
byte[] key = Constants.encode(te.getFullName());
Entry e = new Entry(te, 0);
entries.put(key, e);
return e;
}
/**
* Check out content of the content represented by the index
*
* @param wd
* workdir
* @throws IOException
*/
public void checkout(File wd) throws IOException {
for (Entry e : entries.values()) {
if (e.getStage() != 0)
continue;
checkoutEntry(wd, e);
}
}
/**
* Check out content of the specified index entry
*
* @param wd workdir
* @param e index entry
* @throws IOException
*/
public void checkoutEntry(File wd, Entry e) throws IOException {
ObjectLoader ol = db.open(e.sha1, Constants.OBJ_BLOB);
File file = new File(wd, e.getName());
file.delete();
file.getParentFile().mkdirs();
FileOutputStream dst = new FileOutputStream(file);
try {
ol.copyTo(dst);
} finally {
dst.close();
}
if (config_filemode() && File_hasExecute()) {
if (FileMode.EXECUTABLE_FILE.equals(e.mode)) {
if (!File_canExecute(file))
File_setExecute(file, true);
} else {
if (File_canExecute(file))
File_setExecute(file, false);
}
}
e.mtime = file.lastModified() * 1000000L;
e.ctime = e.mtime;
}
/**
* Construct and write tree out of index.
*
* @return SHA-1 of the constructed tree
*
* @throws IOException
*/
public ObjectId writeTree() throws IOException {
checkWriteOk();
ObjectWriter writer = new ObjectWriter(db);
Tree current = new Tree(db);
Stack<Tree> trees = new Stack<Tree>();
trees.push(current);
String[] prevName = new String[0];
for (Entry e : entries.values()) {
if (e.getStage() != 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, Constants.encode(newName[trees.size() - 1]));
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,
Constants.encode(newName[newName.length - 1]),
(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 = 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;
}
/**
* Return the members of the index sorted by the unsigned byte
* values of the path names.
*
* Small beware: Unaccounted for are unmerged entries. You may want
* to abort if members with stage != 0 are found if you are doing
* any updating operations. All stages will be found after one another
* here later. Currently only one stage per name is returned.
*
* @return The index entries sorted
*/
public Entry[] getMembers() {
return entries.values().toArray(new Entry[entries.size()]);
}
/**
* Look up an entry with the specified path.
*
* @param path
* @return index entry for the path or null if not in index.
* @throws UnsupportedEncodingException
*/
public Entry getEntry(String path) throws UnsupportedEncodingException {
return entries.get(Repository.gitInternalSlash(Constants.encode(path)));
}
/**
* @return The repository holding this index.
*/
public Repository getRepository() {
return db;
}
}