1 package org
.spearce
.jgit
.lib
;
4 import java
.io
.FileInputStream
;
5 import java
.io
.FileNotFoundException
;
6 import java
.io
.FileOutputStream
;
7 import java
.io
.IOException
;
8 import java
.io
.InputStream
;
9 import java
.io
.RandomAccessFile
;
10 import java
.io
.UnsupportedEncodingException
;
11 import java
.lang
.reflect
.InvocationTargetException
;
12 import java
.lang
.reflect
.Method
;
13 import java
.nio
.ByteBuffer
;
14 import java
.nio
.ByteOrder
;
15 import java
.nio
.MappedByteBuffer
;
16 import java
.nio
.channels
.FileChannel
;
17 import java
.nio
.channels
.FileChannel
.MapMode
;
18 import java
.security
.MessageDigest
;
19 import java
.util
.Comparator
;
20 import java
.util
.Date
;
21 import java
.util
.Iterator
;
23 import java
.util
.Stack
;
24 import java
.util
.TreeMap
;
26 import org
.spearce
.jgit
.errors
.CorruptObjectException
;
28 public class GitIndex
{
30 private RandomAccessFile cache
;
32 private File cacheFile
;
35 private boolean changed
;
37 // Stat information updated
38 private boolean statDirty
;
40 private Header header
;
42 private long lastCacheTime
;
44 private final Repository db
;
46 private Map entries
= new TreeMap(new Comparator() {
47 public int compare(Object arg0
, Object arg1
) {
48 byte[] a
= (byte[]) arg0
;
49 byte[] b
= (byte[]) arg1
;
50 for (int i
= 0; i
< a
.length
&& i
< b
.length
; ++i
) {
55 if (a
.length
< b
.length
)
57 else if (a
.length
> b
.length
)
63 public GitIndex(Repository db
) {
65 this.cacheFile
= new File(db
.getDirectory(), "index");
68 public boolean isChanged() {
69 return changed
|| statDirty
;
72 public void rereadIfNecessary() throws IOException
{
73 if (cacheFile
.exists() && cacheFile
.lastModified() != lastCacheTime
) {
78 public void add(File wd
, File f
) throws IOException
{
79 byte[] key
= Entry
.makeKey(wd
, f
);
80 Entry e
= (Entry
) entries
.get(key
);
82 e
= new Entry(key
, f
, 0, this);
89 public void remove(File wd
, File f
) {
90 byte[] key
= Entry
.makeKey(wd
, f
);
94 public void read() throws IOException
{
95 long t0
= System
.currentTimeMillis();
98 cache
= new RandomAccessFile(cacheFile
, "r");
100 MappedByteBuffer map
= cache
.getChannel().map(MapMode
.READ_ONLY
, 0,
102 map
.order(ByteOrder
.BIG_ENDIAN
);
103 header
= new Header(map
);
105 for (int i
= 0; i
< header
.entries
; ++i
) {
106 Entry entry
= new Entry(this, map
);
107 entries
.put(entry
.name
, entry
);
109 long t1
= System
.currentTimeMillis();
110 lastCacheTime
= cacheFile
.lastModified();
111 System
.out
.println("Read index "+cacheFile
+" in "+((t1
-t0
)/1000.0)+"s");
117 public void write() throws IOException
{
118 File tmpIndex
= new File(cacheFile
.getAbsoluteFile() + ".tmp");
119 File lock
= new File(cacheFile
.getAbsoluteFile() + ".lock");
120 if (!lock
.createNewFile())
121 throw new IOException("Index file is in use");
123 FileOutputStream fileOutputStream
= new FileOutputStream(tmpIndex
);
124 FileChannel fc
= fileOutputStream
.getChannel();
125 ByteBuffer buf
= ByteBuffer
.allocate(4096);
126 MessageDigest newMessageDigest
= Constants
.newMessageDigest();
127 header
= new Header(entries
);
131 .update(buf
.array(), buf
.arrayOffset(), buf
.limit());
135 for (Iterator i
= entries
.values().iterator(); i
.hasNext();) {
136 Entry e
= (Entry
) i
.next();
139 newMessageDigest
.update(buf
.array(), buf
.arrayOffset(), buf
145 buf
.put(newMessageDigest
.digest());
149 fileOutputStream
.close();
150 if (!tmpIndex
.renameTo(cacheFile
))
151 throw new IOException(
152 "Could not rename temporary index file to index");
157 throw new IOException(
158 "Could not delete lock file. Should not happen");
159 if (tmpIndex
.exists() && !tmpIndex
.delete())
160 throw new IOException(
161 "Could not delete temporary index file. Should not happen");
165 public static class Entry
{
182 private ObjectId sha1
;
190 private GitIndex theIndex
;
192 static byte[] makeKey(File wd
, File f
) {
193 if (!f
.getPath().startsWith(wd
.getPath()))
194 throw new Error("Path is not in working dir");
195 String relName
= f
.getPath().substring(wd
.getPath().length() + 1)
196 .replace(File
.separatorChar
, '/');
197 return relName
.getBytes();
200 public Entry(byte[] key
, File f
, int stage
, GitIndex index
)
203 ctime
= f
.lastModified() * 1000000L;
204 mtime
= ctime
; // we use same here
207 mode
= FileMode
.REGULAR_FILE
.getBits();
210 size
= (int) f
.length();
211 ObjectWriter writer
= new ObjectWriter(theIndex
.db
);
212 sha1
= writer
.writeBlob(f
);
214 flags
= (short) ((stage
<< 12) | name
.length
); // TODO: fix flags
217 public Entry(TreeEntry f
, int stage
, GitIndex index
)
218 throws UnsupportedEncodingException
{
224 mode
= f
.getMode().getBits();
229 name
= f
.getFullName().getBytes("UTF-8");
230 flags
= (short) ((stage
<< 12) | name
.length
); // TODO: fix flags
233 Entry(GitIndex index
, ByteBuffer b
) {
235 int startposition
= b
.position();
236 ctime
= b
.getInt() * 1000000000L + (b
.getInt() % 1000000000L);
237 mtime
= b
.getInt() * 1000000000L + (b
.getInt() % 1000000000L);
244 byte[] sha1bytes
= new byte[Constants
.OBJECT_ID_LENGTH
];
246 sha1
= new ObjectId(sha1bytes
);
247 flags
= b
.getShort();
248 stage
= (flags
& 0x4000) >> 12;
249 name
= new byte[flags
& 0xFFF];
252 .position(startposition
253 + ((8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 20 + 2
254 + name
.length
+ 8) & ~
7));
257 public boolean update(File f
, Repository db
) throws IOException
{
258 boolean modified
= false;
259 if (mtime
!= f
.lastModified())
261 mtime
= f
.lastModified() * 1000000L;
262 if (size
!= f
.length())
264 size
= (int) f
.length();
265 ObjectWriter writer
= new ObjectWriter(db
);
266 ObjectId newsha1
= sha1
= writer
.writeBlob(f
);
267 if (!newsha1
.equals(sha1
))
273 public void write(ByteBuffer buf
) {
274 int startposition
= buf
.position();
275 buf
.putInt((int) (ctime
/ 1000000000L));
276 buf
.putInt((int) (ctime
% 1000000000L));
277 buf
.putInt((int) (mtime
/ 1000000000L));
278 buf
.putInt((int) (mtime
% 1000000000L));
285 buf
.put(sha1
.getBytes());
288 int end
= startposition
289 + ((8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 20 + 2 + name
.length
+ 8) & ~
7);
290 int remain
= end
- buf
.position();
295 static Method canExecute
;
298 canExecute
= File
.class.getMethod("canExecute", (Class
[]) null);
299 } catch (SecurityException e
) {
300 // TODO Auto-generated catch block
302 } catch (NoSuchMethodException e
) {
303 // TODO Auto-generated catch block
309 * JDK1.6 has file.canExecute
311 * if (file.canExecute() != FileMode.EXECUTABLE_FILE.equals(mode))
314 boolean File_canExecute(File f
) {
315 if (canExecute
!= null) {
317 return ((Boolean
) canExecute
.invoke(f
, (Object
[]) null))
319 } catch (IllegalArgumentException e
) {
320 // TODO Auto-generated catch block
323 } catch (IllegalAccessException e
) {
324 // TODO Auto-generated catch block
327 } catch (InvocationTargetException e
) {
328 // TODO Auto-generated catch block
336 public boolean isModified(File wd
) {
337 File file
= getFile(wd
);
341 // JDK1.6 has file.canExecute
342 // if (file.canExecute() != FileMode.EXECUTABLE_FILE.equals(mode))
344 if (FileMode
.EXECUTABLE_FILE
.equals(mode
)) {
345 if (!File_canExecute(file
))
348 if (FileMode
.REGULAR_FILE
.equals(mode
)) {
351 if (File_canExecute(file
))
354 if (FileMode
.SYMLINK
.equals(mode
)) {
357 if (FileMode
.TREE
.equals(mode
)) {
358 if (!file
.isDirectory())
361 System
.out
.println("Does not handle mode "+mode
+" ("+file
+")");
368 long javamtime
= mtime
/ 1000000L;
369 long lastm
= file
.lastModified();
370 if (file
.length() != size
)
372 if (lastm
!= javamtime
) {
374 InputStream is
= new FileInputStream(file
);
375 ObjectWriter objectWriter
= new ObjectWriter(theIndex
.db
);
377 ObjectId newId
= objectWriter
.computeBlobSha1(file
379 boolean ret
= !newId
.equals(sha1
);
380 theIndex
.statDirty
= true;
382 } catch (IOException e
) {
387 } catch (IOException e
) {
388 // can't happen, but if it does we ignore it
392 } catch (FileNotFoundException e
) {
393 // should not happen because we already checked this
401 private File
getFile(File wd
) {
402 return new File(wd
, getName());
405 public String
toString() {
406 return new String(name
) + "/SHA-1(" + sha1
+ ")/M:"
407 + new Date(ctime
/ 1000000L) + "/C:"
408 + new Date(mtime
/ 1000000L) + "/d" + dev
+ "/i" + ino
409 + "/m" + Integer
.toString(mode
, 8) + "/u" + uid
+ "/g"
410 + gid
+ "/s" + size
+ "/f" + flags
+ "/@" + stage
;
413 public String
getName() {
414 return new String(name
);
417 public ObjectId
getObjectId() {
422 static class Header
{
423 private int signature
;
429 public Header(ByteBuffer map
) throws CorruptObjectException
{
433 private void read(ByteBuffer buf
) throws CorruptObjectException
{
434 signature
= buf
.getInt();
435 version
= buf
.getInt();
436 entries
= buf
.getInt();
437 if (signature
!= 0x44495243)
438 throw new CorruptObjectException("Index signature is invalid: "
441 throw new CorruptObjectException(
442 "Unknow index version (or corrupt index):" + version
);
445 public void write(ByteBuffer buf
) {
446 buf
.order(ByteOrder
.BIG_ENDIAN
);
447 buf
.putInt(signature
);
452 public Header(Map entryset
) {
453 signature
= 0x44495243;
455 entries
= entryset
.size();
459 public void readTree(Tree t
) throws IOException
{
463 public void readTree(String prefix
, Tree t
) throws IOException
{
464 TreeEntry
[] members
= t
.members();
465 for (int i
= 0; i
< members
.length
; ++i
) {
466 TreeEntry te
= members
[i
];
468 if (prefix
.length() > 0)
469 name
= prefix
+ "/" + te
.getName();
472 if (te
instanceof Tree
) {
473 readTree(name
, (Tree
) te
);
475 Entry e
= new Entry(te
, 0, this);
476 entries
.put(name
.getBytes("UTF-8"), e
);
481 public void checkout(File wd
) throws IOException
{
482 for (Iterator i
= entries
.values().iterator(); i
.hasNext();) {
483 Entry e
= (Entry
) i
.next();
486 ObjectLoader ol
= db
.openBlob(e
.sha1
);
487 byte[] bytes
= ol
.getBytes();
488 File file
= new File(wd
, e
.getName());
490 file
.getParentFile().mkdirs();
491 FileChannel channel
= new FileOutputStream(file
).getChannel();
492 ByteBuffer buffer
= ByteBuffer
.wrap(bytes
);
493 int j
= channel
.write(buffer
);
494 if (j
!= bytes
.length
)
495 throw new IOException("Could not write file " + file
);
500 public ObjectId
writeTree(File wd
) throws IOException
{
501 ObjectWriter writer
= new ObjectWriter(db
);
502 Tree current
= new Tree(db
);
503 Stack trees
= new Stack();
505 String
[] prevName
= new String
[0];
506 for (Iterator i
= entries
.values().iterator(); i
.hasNext();) {
507 Entry e
= (Entry
) i
.next();
510 String
[] newName
= splitDirPath(e
.getName());
511 int c
= longestCommonPath(prevName
, newName
);
512 while (c
< trees
.size() - 1) {
513 current
.setId(writer
.writeTree(current
));
515 current
= trees
.isEmpty() ?
null : (Tree
) trees
.peek();
517 while (trees
.size() < newName
.length
) {
518 if (!current
.existsTree(newName
[trees
.size() - 1])) {
519 current
= new Tree(current
, newName
[trees
.size() - 1]
521 current
.getParent().addEntry(current
);
524 current
= (Tree
) current
.findTreeMember(newName
[trees
529 FileTreeEntry ne
= new FileTreeEntry(current
, e
.sha1
,
530 newName
[newName
.length
- 1].getBytes(),
531 (e
.mode
& FileMode
.EXECUTABLE_FILE
.getBits()) == FileMode
.EXECUTABLE_FILE
.getBits());
532 current
.addEntry(ne
);
534 while (!trees
.isEmpty()) {
535 current
.setId(writer
.writeTree(current
));
537 if (!trees
.isEmpty())
538 current
= (Tree
) trees
.peek();
540 return current
.getTreeId();
543 String
[] splitDirPath(String name
) {
544 String
[] tmp
= new String
[name
.length() / 2 + 1];
548 while ((p1
= name
.indexOf('/', p0
+ 1)) != -1) {
549 tmp
[c
++] = name
.substring(p0
+ 1, p1
);
552 tmp
[c
++] = name
.substring(p0
+ 1);
553 String
[] ret
= new String
[c
];
554 for (int i
= 0; i
< c
; ++i
) {
560 int longestCommonPath(String
[] a
, String
[] b
) {
562 for (i
= 0; i
< a
.length
&& i
< b
.length
; ++i
)
563 if (!a
[i
].equals(b
[i
]))
568 public Entry
[] getMembers() {
569 return (Entry
[]) entries
.values().toArray(new Entry
[entries
.size()]);
572 public Entry
getEntry(String path
) throws UnsupportedEncodingException
{
573 return (Entry
) entries
.get(path
.getBytes("ISO-8859-1"));