git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [EGIT PATCH 00/26] New DirCache API
@ 2008-08-12  1:07 Shawn O. Pearce
  2008-08-12  1:07 ` [EGIT PATCH 01/26] Force all source code to UTF-8 encoding by default Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

This is (finally) my long-promised DirCache API.  It has been evolving
and growing since before April I think.  Its finally cleaned up enough
for public viewing and usage.

I started this change because I didn't see an easy way to plug
GitIndex into TreeWalk, I found GitIndex to be too slow, and it
required direct access to the working directory in order to handle
certain operations.  DirCache has none of these issues.

The API is meant to replace GitIndex, as we can now merge-sort a 'DIRC'
(aka .git/index) file against any other tree as part of any active
TreeWalk instance.  This makes it really easy to do n-way diffs against
commits, one or more index files, and the working directory.

The implementation is leaner and meaner than GitIndex.  It avoids a
lot of unnecessary conversion overheads, and it tries to reduce the
per-entry memory usage as much as possible.

We almost support the 'TREE' cache extension.  Updates to the index
through DirCacheBuilder or DirCacheEditor wind up throwing away the
entire 'TREE' extension, instead of invalidating only those trees
we know to be dirty.  This is certainly suboptimal and is an area
for future improvement.

>From here I'm going to start building 3 way merge support onto
this API, by taking advantage of TreeWalk to do the parallel
tree unpacks necessary for the merge operation.

Yes, its ~4.5k lines (excluding some test vectors).  However it
will allow us to eventually remove about 2k lines from jgit alone:

  AbstractIndexTreeVisitor  74
  GitIndex                 912
  IndexDiff                160
  IndexTreeWalker          241
  IndexTreeVisitor          97
  TreeIterator             203
  WorkDirCheckout          401
  ----------------------------
                          2088

Other classes like GitResourceDecorator simplify I think as a result
of this new API being available.  I have only started to port that
over, but found I was getting side-tracked in porting the existing
GitIndex users and I really want to focus on the merge code instead.

So overall we may see about a 2k line increase as a result of this
API being used in the tree rather than GitIndex, but since it fits
into the overall TreeWalk architecture its better suited for reuse,
so we may see more than 2k saved over time.


Shawn O. Pearce (26):
  Force all source code to UTF-8 encoding by default
  Protect WorkingTreeIterator's name encoding from weird ByteBuffers
  Add Constants.encode as a utility for quick encoding in UTF-8
  Rely upon Constants.CHARSET over Constants.CHARACTER_ENCODING
  Allow AbstractTreeIterators to find out about StopWalkExceptions
  Implement a new .git/index (aka dircache) read interface
  Export the new DirCache API to Eclipse plugins using jgit
  Support locking (and unlocking) a .git/index through DirCache
  Support writing a .git/index through DirCache
  Support the 'TREE' extension in .git/index through DirCache
  Support using a DirCache within a TreeWalk
  Support recreating a .git/index through DirCache
  Support iterating and building a DirCache at the same time
  Support creating a new DirCacheEntry for an arbitrary path
  Support a simplified model of editing index entries
  Support recursively getting all entries under a subtree path
  Support copying meta fields from one DirCacheEntry to another
  Add JUnit tests for new DirCache API
  Add JUnit tests for DirCache compatibility with C Git
  Allow the new DirCacheIterator in command line arguments
  Add debugging commands to interact with the new DirCache code
  Add a basic command line implementation of rm
  Rewrite GitMoveDeleteHook to use DirCacheBuilder
  Teach GitMoveDeleteHook how to move a folder recursively
  Rewrite UntrackOperation to use DirCacheBuilder
  Rewrite AssumeUnchangedOperation to use DirCache

 .../.settings/org.eclipse.core.resources.prefs     |    3 +
 .../.settings/org.eclipse.core.resources.prefs     |    3 +
 .../.settings/org.eclipse.core.resources.prefs     |    3 +
 .../.settings/org.eclipse.core.resources.prefs     |    3 +
 .../org/spearce/egit/core/GitMoveDeleteHook.java   |  156 ++-
 .../egit/core/op/AssumeUnchangedOperation.java     |  133 +-
 .../org/spearce/egit/core/op/UntrackOperation.java |   98 +-
 .../.settings/org.eclipse.core.resources.prefs     |    3 +
 .../.settings/org.eclipse.core.resources.prefs     |    3 +
 .../.settings/org.eclipse.core.resources.prefs     |    3 +
 .../services/org.spearce.jgit.pgm.TextBuiltin      |    6 +
 .../src/org/spearce/jgit/pgm/Rm.java               |   92 ++
 .../org/spearce/jgit/pgm/debug/MakeCacheTree.java  |   67 +
 .../org/spearce/jgit/pgm/debug/ReadDirCache.java   |   53 +
 .../org/spearce/jgit/pgm/debug/ShowCacheTree.java  |   69 +
 .../org/spearce/jgit/pgm/debug/ShowDirCache.java   |   72 +
 .../org/spearce/jgit/pgm/debug/WriteDirCache.java  |   54 +
 .../jgit/pgm/opt/AbstractTreeIteratorHandler.java  |   13 +
 .../.settings/org.eclipse.core.resources.prefs     |    3 +
 .../spearce/jgit/dircache/DirCacheBasicTest.java   |  185 +++
 .../jgit/dircache/DirCacheBuilderIteratorTest.java |   91 ++
 .../spearce/jgit/dircache/DirCacheBuilderTest.java |  253 ++++
 .../dircache/DirCacheCGitCompatabilityTest.java    |  204 +++
 .../spearce/jgit/dircache/DirCacheFindTest.java    |   86 ++
 .../jgit/dircache/DirCacheIteratorTest.java        |  273 ++++
 .../spearce/jgit/dircache/DirCacheTreeTest.java    |  150 ++
 .../tst/org/spearce/jgit/dircache/gitgit.index     |  Bin 0 -> 134799 bytes
 .../tst/org/spearce/jgit/dircache/gitgit.lsfiles   | 1437 ++++++++++++++++++++
 .../tst/org/spearce/jgit/dircache/gitgit.lstree    |  331 +++++
 .../spearce/jgit/lib/ConstantsEncodingTest.java    |   89 ++
 .../.settings/org.eclipse.core.resources.prefs     |    3 +
 org.spearce.jgit/META-INF/MANIFEST.MF              |    3 +-
 .../spearce/jgit/dircache/BaseDirCacheEditor.java  |  194 +++
 .../src/org/spearce/jgit/dircache/DirCache.java    |  681 +++++++++
 .../jgit/dircache/DirCacheBuildIterator.java       |  124 ++
 .../org/spearce/jgit/dircache/DirCacheBuilder.java |  175 +++
 .../org/spearce/jgit/dircache/DirCacheEditor.java  |  265 ++++
 .../org/spearce/jgit/dircache/DirCacheEntry.java   |  385 ++++++
 .../spearce/jgit/dircache/DirCacheIterator.java    |  205 +++
 .../org/spearce/jgit/dircache/DirCacheTree.java    |  453 ++++++
 .../src/org/spearce/jgit/lib/Constants.java        |   25 +
 .../src/org/spearce/jgit/lib/ObjectWriter.java     |    2 +-
 .../src/org/spearce/jgit/lib/RefDatabase.java      |    9 +-
 .../src/org/spearce/jgit/lib/RepositoryConfig.java |    4 +-
 .../org/spearce/jgit/transport/PacketLineIn.java   |    3 +-
 .../jgit/transport/SideBandInputStream.java        |    3 +-
 .../spearce/jgit/transport/TransportBundle.java    |    3 +-
 .../spearce/jgit/transport/WalkPushConnection.java |    4 +-
 .../jgit/transport/WalkRemoteObjectDatabase.java   |    5 +-
 .../jgit/treewalk/AbstractTreeIterator.java        |   11 +
 .../spearce/jgit/treewalk/EmptyTreeIterator.java   |    6 +
 .../src/org/spearce/jgit/treewalk/TreeWalk.java    |   12 +-
 .../spearce/jgit/treewalk/WorkingTreeIterator.java |    2 +-
 .../spearce/jgit/treewalk/filter/PathFilter.java   |   10 +-
 54 files changed, 6333 insertions(+), 190 deletions(-)
 create mode 100644 org.spearce.egit-feature/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.egit-updatesite/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.egit.core.test/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.egit.core/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.egit.ui/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.egit/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.jgit.pgm/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Rm.java
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/MakeCacheTree.java
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ReadDirCache.java
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ShowCacheTree.java
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ShowDirCache.java
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/WriteDirCache.java
 create mode 100644 org.spearce.jgit.test/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBasicTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBuilderIteratorTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBuilderTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheCGitCompatabilityTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheFindTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheIteratorTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheTreeTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/gitgit.index
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/gitgit.lsfiles
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/gitgit.lstree
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/lib/ConstantsEncodingTest.java
 create mode 100644 org.spearce.jgit/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/BaseDirCacheEditor.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuildIterator.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEditor.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheIterator.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheTree.java

^ permalink raw reply	[flat|nested] 26+ messages in thread

* [EGIT PATCH 01/26] Force all source code to UTF-8 encoding by default
  2008-08-12  1:07 [EGIT PATCH 00/26] New DirCache API Shawn O. Pearce
@ 2008-08-12  1:07 ` Shawn O. Pearce
  2008-08-12  1:07   ` [EGIT PATCH 02/26] Protect WorkingTreeIterator's name encoding from weird ByteBuffers Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

To keep things simple we should use a single character encoding
for all of our source files, and UTF-8 is the best choice as it
will handle any possible Unicode character but the majority of
our source is plain 'ole ASCII.

We really want to support full Unicode in our sources so we can
setup Unicode test vectors, have translations which are usually
in the standard encoding, and correctly support author names in
source code copyright comments.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../.settings/org.eclipse.core.resources.prefs     |    3 +++
 .../.settings/org.eclipse.core.resources.prefs     |    3 +++
 .../.settings/org.eclipse.core.resources.prefs     |    3 +++
 .../.settings/org.eclipse.core.resources.prefs     |    3 +++
 .../.settings/org.eclipse.core.resources.prefs     |    3 +++
 .../.settings/org.eclipse.core.resources.prefs     |    3 +++
 .../.settings/org.eclipse.core.resources.prefs     |    3 +++
 .../.settings/org.eclipse.core.resources.prefs     |    3 +++
 .../.settings/org.eclipse.core.resources.prefs     |    3 +++
 9 files changed, 27 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.egit-feature/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.egit-updatesite/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.egit.core.test/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.egit.core/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.egit.ui/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.egit/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.jgit.pgm/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.jgit.test/.settings/org.eclipse.core.resources.prefs
 create mode 100644 org.spearce.jgit/.settings/org.eclipse.core.resources.prefs

diff --git a/org.spearce.egit-feature/.settings/org.eclipse.core.resources.prefs b/org.spearce.egit-feature/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..8d266d9
--- /dev/null
+++ b/org.spearce.egit-feature/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Mon Aug 11 16:46:45 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.spearce.egit-updatesite/.settings/org.eclipse.core.resources.prefs b/org.spearce.egit-updatesite/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..4ec836b
--- /dev/null
+++ b/org.spearce.egit-updatesite/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Mon Aug 11 16:46:54 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.spearce.egit.core.test/.settings/org.eclipse.core.resources.prefs b/org.spearce.egit.core.test/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..84e44d8
--- /dev/null
+++ b/org.spearce.egit.core.test/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Mon Aug 11 16:47:12 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.spearce.egit.core/.settings/org.eclipse.core.resources.prefs b/org.spearce.egit.core/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..d4ff695
--- /dev/null
+++ b/org.spearce.egit.core/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Mon Aug 11 16:47:03 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.spearce.egit.ui/.settings/org.eclipse.core.resources.prefs b/org.spearce.egit.ui/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..2f242b3
--- /dev/null
+++ b/org.spearce.egit.ui/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Mon Aug 11 16:47:19 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.spearce.egit/.settings/org.eclipse.core.resources.prefs b/org.spearce.egit/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..78f271d
--- /dev/null
+++ b/org.spearce.egit/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Mon Aug 11 16:46:35 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.spearce.jgit.pgm/.settings/org.eclipse.core.resources.prefs b/org.spearce.jgit.pgm/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..759548b
--- /dev/null
+++ b/org.spearce.jgit.pgm/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Mon Aug 11 16:46:23 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.spearce.jgit.test/.settings/org.eclipse.core.resources.prefs b/org.spearce.jgit.test/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..a2724ba
--- /dev/null
+++ b/org.spearce.jgit.test/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Mon Aug 11 16:05:15 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/org.spearce.jgit/.settings/org.eclipse.core.resources.prefs b/org.spearce.jgit/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..66ac15c
--- /dev/null
+++ b/org.spearce.jgit/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Mon Aug 11 16:46:12 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 02/26] Protect WorkingTreeIterator's name encoding from weird ByteBuffers
  2008-08-12  1:07 ` [EGIT PATCH 01/26] Force all source code to UTF-8 encoding by default Shawn O. Pearce
@ 2008-08-12  1:07   ` Shawn O. Pearce
  2008-08-12  1:07     ` [EGIT PATCH 03/26] Add Constants.encode as a utility for quick encoding in UTF-8 Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

If a ByteBuffer decides to be cute and give us an array but use
an array offset that isn't zero we cannot use its array as our
own encodedName array.  Instead we must create a copy of the array
so we can safely assume where the path starts.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../spearce/jgit/treewalk/WorkingTreeIterator.java |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/WorkingTreeIterator.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/WorkingTreeIterator.java
index 4ac711b..356222b 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/WorkingTreeIterator.java
@@ -415,7 +415,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
 				throws CharacterCodingException {
 			final ByteBuffer b = enc.encode(CharBuffer.wrap(getName()));
 			encodedNameLen = b.limit();
-			if (b.hasArray())
+			if (b.hasArray() && b.arrayOffset() == 0)
 				encodedName = b.array();
 			else
 				b.get(encodedName = new byte[encodedNameLen]);
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 03/26] Add Constants.encode as a utility for quick encoding in UTF-8
  2008-08-12  1:07   ` [EGIT PATCH 02/26] Protect WorkingTreeIterator's name encoding from weird ByteBuffers Shawn O. Pearce
@ 2008-08-12  1:07     ` Shawn O. Pearce
  2008-08-12  1:07       ` [EGIT PATCH 04/26] Rely upon Constants.CHARSET over Constants.CHARACTER_ENCODING Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

We often need to convert a string into a UTF-8 encoding, so that
we can use this string as a path filter in a TreeWalk or in some
other suitable place where we assume a standard UTF-8 encoding is
being used.  As we have already done the lookup for the CHARSET
we can reuse that same CHARSET reference during future encoding
calls, while allowing the CharSet implementation to cache and
reuse the actual encoder instance.

Whenever possible we try to avoid copying the result as most of
the time the returned ByteBuffer's internal array matches the
result array we need to return to our caller.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../spearce/jgit/lib/ConstantsEncodingTest.java    |   89 ++++++++++++++++++++
 .../src/org/spearce/jgit/lib/Constants.java        |   25 ++++++
 2 files changed, 114 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/lib/ConstantsEncodingTest.java

diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ConstantsEncodingTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ConstantsEncodingTest.java
new file mode 100644
index 0000000..7b3e5a0
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ConstantsEncodingTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.lib;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+public class ConstantsEncodingTest extends TestCase {
+	public void testEncodeASCII_SimpleASCII()
+			throws UnsupportedEncodingException {
+		final String src = "abc";
+		final byte[] exp = { 'a', 'b', 'c' };
+		final byte[] res = Constants.encodeASCII(src);
+		assertTrue(Arrays.equals(exp, res));
+		assertEquals(src, new String(res, 0, res.length, "UTF-8"));
+	}
+
+	public void testEncodeASCII_FailOnNonASCII() {
+		final String src = "Ūnĭcōde̽";
+		try {
+			Constants.encodeASCII(src);
+			fail("Incorrectly accepted a Unicode character");
+		} catch (IllegalArgumentException err) {
+			assertEquals("Not ASCII string: " + src, err.getMessage());
+		}
+	}
+
+	public void testEncodeASCII_Number13() {
+		final long src = 13;
+		final byte[] exp = { '1', '3' };
+		final byte[] res = Constants.encodeASCII(src);
+		assertTrue(Arrays.equals(exp, res));
+	}
+
+	public void testEncode_SimpleASCII() throws UnsupportedEncodingException {
+		final String src = "abc";
+		final byte[] exp = { 'a', 'b', 'c' };
+		final byte[] res = Constants.encode(src);
+		assertTrue(Arrays.equals(exp, res));
+		assertEquals(src, new String(res, 0, res.length, "UTF-8"));
+	}
+
+	public void testEncode_Unicode() throws UnsupportedEncodingException {
+		final String src = "Ūnĭcōde̽";
+		final byte[] exp = { (byte) 0xC5, (byte) 0xAA, 0x6E, (byte) 0xC4,
+				(byte) 0xAD, 0x63, (byte) 0xC5, (byte) 0x8D, 0x64, 0x65,
+				(byte) 0xCC, (byte) 0xBD };
+		final byte[] res = Constants.encode(src);
+		assertTrue(Arrays.equals(exp, res));
+		assertEquals(src, new String(res, 0, res.length, "UTF-8"));
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Constants.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Constants.java
index 7c2cef9..23ac3ac 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Constants.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Constants.java
@@ -1,6 +1,7 @@
 /*
  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Google Inc.
  *
  * All rights reserved.
  *
@@ -38,6 +39,7 @@
 
 package org.spearce.jgit.lib;
 
+import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
@@ -387,6 +389,29 @@ public final class Constants {
 		return r;
 	}
 
+	/**
+	 * Convert a string to a byte array in the standard character encoding.
+	 * 
+	 * @param str
+	 *            the string to convert. May contain any Unicode characters.
+	 * @return a byte array representing the requested string, encoded using the
+	 *         default character encoding (UTF-8).
+	 * @see #CHARACTER_ENCODING
+	 */
+	public static byte[] encode(final String str) {
+		final ByteBuffer bb = Constants.CHARSET.encode(str);
+		final int len = bb.limit();
+		if (bb.hasArray() && bb.arrayOffset() == 0) {
+			final byte[] arr = bb.array();
+			if (arr.length == len)
+				return arr;
+		}
+
+		final byte[] arr = new byte[len];
+		bb.get(arr);
+		return arr;
+	}
+
 	static {
 		if (OBJECT_ID_LENGTH != newMessageDigest().getDigestLength())
 			throw new LinkageError("Incorrect OBJECT_ID_LENGTH.");
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 04/26] Rely upon Constants.CHARSET over Constants.CHARACTER_ENCODING
  2008-08-12  1:07     ` [EGIT PATCH 03/26] Add Constants.encode as a utility for quick encoding in UTF-8 Shawn O. Pearce
@ 2008-08-12  1:07       ` Shawn O. Pearce
  2008-08-12  1:07         ` [EGIT PATCH 05/26] Allow AbstractTreeIterators to find out about StopWalkExceptions Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

This avoids the UnsupportedEncodingException error which we know
must not fail on a JRE that supports jgit.  We have to have the
stock UTF-8 encoding available for basic operation.

In some cases we can also avoid an unnecessary copy.  Some code
paths in the byte[]->String utility methods in the Sun JRE seem
to copy the input byte[] prior to calling the character set for
the conversion.  This copy is unnecessary.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../src/org/spearce/jgit/lib/ObjectWriter.java     |    2 +-
 .../src/org/spearce/jgit/lib/RefDatabase.java      |    9 +++------
 .../src/org/spearce/jgit/lib/RepositoryConfig.java |    4 ++--
 .../org/spearce/jgit/transport/PacketLineIn.java   |    3 ++-
 .../jgit/transport/SideBandInputStream.java        |    3 ++-
 .../spearce/jgit/transport/TransportBundle.java    |    3 ++-
 .../spearce/jgit/transport/WalkPushConnection.java |    4 ++--
 .../jgit/transport/WalkRemoteObjectDatabase.java   |    5 ++---
 .../src/org/spearce/jgit/treewalk/TreeWalk.java    |   10 ++--------
 .../spearce/jgit/treewalk/filter/PathFilter.java   |   10 +---------
 10 files changed, 19 insertions(+), 34 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectWriter.java b/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectWriter.java
index be2e28c..6c2cd4f 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectWriter.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectWriter.java
@@ -236,7 +236,7 @@ public class ObjectWriter {
 	public ObjectId writeTag(final Tag c) throws IOException {
 		final ByteArrayOutputStream os = new ByteArrayOutputStream();
 		final OutputStreamWriter w = new OutputStreamWriter(os,
-				Constants.CHARACTER_ENCODING);
+				Constants.CHARSET);
 
 		w.write("object ");
 		c.getObjId().copyTo(w);
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
index 3e68a8d..facd3e4 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefDatabase.java
@@ -45,7 +45,6 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -54,8 +53,6 @@ import org.spearce.jgit.util.FS;
 import org.spearce.jgit.util.NB;
 
 class RefDatabase {
-	private static final String CHAR_ENC = Constants.CHARACTER_ENCODING;
-
 	private static final String REFS_SLASH = "refs/";
 
 	private static final String HEADS_SLASH = Constants.HEADS_PREFIX + "/";
@@ -157,7 +154,7 @@ class RefDatabase {
 	 * @throws IOException
 	 */
 	void link(final String name, final String target) throws IOException {
-		final byte[] content = ("ref: " + target + "\n").getBytes(CHAR_ENC);
+		final byte[] content = Constants.encode("ref: " + target + "\n");
 		final LockFile lck = new LockFile(fileForRef(name));
 		if (!lck.lock())
 			throw new ObjectWritingException("Unable to lock " + name);
@@ -438,9 +435,9 @@ class RefDatabase {
 	}
 
 	private static BufferedReader openReader(final File fileLocation)
-			throws UnsupportedEncodingException, FileNotFoundException {
+			throws FileNotFoundException {
 		return new BufferedReader(new InputStreamReader(new FileInputStream(
-				fileLocation), CHAR_ENC));
+				fileLocation), Constants.CHARSET));
 	}
 
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
index d1cd5fc..048940d 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
@@ -517,7 +517,7 @@ public class RepositoryConfig {
 				+ ".lock");
 		final PrintWriter r = new PrintWriter(new BufferedWriter(
 				new OutputStreamWriter(new FileOutputStream(tmp),
-						Constants.CHARACTER_ENCODING))) {
+						Constants.CHARSET))) {
 			@Override
 			public void println() {
 				print('\n');
@@ -585,7 +585,7 @@ public class RepositoryConfig {
 		clear();
 		readFile = true;
 		final BufferedReader r = new BufferedReader(new InputStreamReader(
-				new FileInputStream(configFile), Constants.CHARACTER_ENCODING));
+				new FileInputStream(configFile), Constants.CHARSET));
 		try {
 			Entry last = null;
 			Entry e = new Entry();
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java b/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java
index f330638..f87517d 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/PacketLineIn.java
@@ -47,6 +47,7 @@ import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.MutableObjectId;
 import org.spearce.jgit.lib.ProgressMonitor;
 import org.spearce.jgit.util.NB;
+import org.spearce.jgit.util.RawParseUtils;
 
 class PacketLineIn {
 	private static final byte fromhex[];
@@ -107,7 +108,7 @@ class PacketLineIn {
 		final byte[] raw = new byte[len];
 		NB.readFully(in, raw, 0, len);
 		readLF();
-		return new String(raw, 0, len, Constants.CHARACTER_ENCODING);
+		return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
 	}
 
 	private void readLF() throws IOException {
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandInputStream.java b/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandInputStream.java
index 9ccf5ad..3ec9bff 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandInputStream.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/SideBandInputStream.java
@@ -48,6 +48,7 @@ import org.spearce.jgit.errors.TransportException;
 import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.ProgressMonitor;
 import org.spearce.jgit.util.NB;
+import org.spearce.jgit.util.RawParseUtils;
 
 /**
  * Unmultiplexes the data portion of a side-band channel.
@@ -194,6 +195,6 @@ class SideBandInputStream extends InputStream {
 	private String readString(final int len) throws IOException {
 		final byte[] raw = new byte[len];
 		NB.readFully(in, raw, 0, len);
-		return new String(raw, 0, len, Constants.CHARACTER_ENCODING);
+		return RawParseUtils.decode(Constants.CHARSET, raw, 0, len);
 	}
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportBundle.java b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportBundle.java
index 24d49eb..d8c2ba4 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/TransportBundle.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/TransportBundle.java
@@ -58,6 +58,7 @@ import org.spearce.jgit.lib.ProgressMonitor;
 import org.spearce.jgit.lib.Ref;
 import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.util.FS;
+import org.spearce.jgit.util.RawParseUtils;
 
 /**
  * Supports fetching from a git bundle (sneaker-net object transport).
@@ -193,7 +194,7 @@ class TransportBundle extends PackTransport {
 			bin.skip(lf);
 			if (lf < cnt && hdrbuf[lf] == '\n')
 				bin.skip(1);
-			return new String(hdrbuf, 0, lf, Constants.CHARACTER_ENCODING);
+			return RawParseUtils.decode(Constants.CHARSET, hdrbuf, 0, lf);
 		}
 
 		@Override
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java
index 904a699..85bbc14 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkPushConnection.java
@@ -325,7 +325,7 @@ class WalkPushConnection extends BaseConnection implements PushConnection {
 			throws TransportException {
 		try {
 			final String ref = "ref: " + pickHEAD(updates) + "\n";
-			final byte[] bytes = ref.getBytes(Constants.CHARACTER_ENCODING);
+			final byte[] bytes = Constants.encode(ref);
 			dest.writeFile("../HEAD", bytes);
 		} catch (IOException e) {
 			throw new TransportException(uri, "cannot create HEAD", e);
@@ -334,7 +334,7 @@ class WalkPushConnection extends BaseConnection implements PushConnection {
 		try {
 			final String config = "[core]\n"
 					+ "\trepositoryformatversion = 0\n";
-			final byte[] bytes = config.getBytes(Constants.CHARACTER_ENCODING);
+			final byte[] bytes = Constants.encode(config);
 			dest.writeFile("../config", bytes);
 		} catch (IOException e) {
 			throw new TransportException(uri, "cannot create config", e);
diff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkRemoteObjectDatabase.java b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkRemoteObjectDatabase.java
index c5a5199..ababc69 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/transport/WalkRemoteObjectDatabase.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/WalkRemoteObjectDatabase.java
@@ -67,8 +67,6 @@ import org.spearce.jgit.util.NB;
  * independent {@link WalkFetchConnection}.
  */
 abstract class WalkRemoteObjectDatabase {
-	static final String CHARENC = Constants.CHARACTER_ENCODING;
-
 	static final String INFO_PACKS = "info/packs";
 
 	static final String INFO_ALTERNATES = "info/alternates";
@@ -448,7 +446,8 @@ abstract class WalkRemoteObjectDatabase {
 	 *             stream could be created.
 	 */
 	BufferedReader openReader(final String path) throws IOException {
-		return new BufferedReader(new InputStreamReader(open(path).in, CHARENC));
+		final InputStream is = open(path).in;
+		return new BufferedReader(new InputStreamReader(is, Constants.CHARSET));
 	}
 
 	/**
diff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
index 9f373e6..d4050ec 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
@@ -38,7 +38,6 @@
 package org.spearce.jgit.treewalk;
 
 import java.io.IOException;
-import java.io.UnsupportedEncodingException;
 import java.util.Collections;
 
 import org.spearce.jgit.errors.CorruptObjectException;
@@ -52,6 +51,7 @@ import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.revwalk.RevTree;
 import org.spearce.jgit.treewalk.filter.PathFilterGroup;
 import org.spearce.jgit.treewalk.filter.TreeFilter;
+import org.spearce.jgit.util.RawParseUtils;
 
 /**
  * Walks one or more {@link AbstractTreeIterator}s in parallel.
@@ -685,12 +685,6 @@ public class TreeWalk {
 	}
 
 	private static String pathOf(final AbstractTreeIterator t) {
-		try {
-			return new String(t.path, 0, t.pathLen,
-					Constants.CHARACTER_ENCODING);
-		} catch (UnsupportedEncodingException uee) {
-			throw new RuntimeException("JVM doesn't support "
-					+ Constants.CHARACTER_ENCODING, uee);
-		}
+		return RawParseUtils.decode(Constants.CHARSET, t.path, 0, t.pathLen);
 	}
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/filter/PathFilter.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/filter/PathFilter.java
index 3aff145..56773a3 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/filter/PathFilter.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/filter/PathFilter.java
@@ -37,8 +37,6 @@
 
 package org.spearce.jgit.treewalk.filter;
 
-import java.io.UnsupportedEncodingException;
-
 import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.treewalk.TreeWalk;
 
@@ -81,13 +79,7 @@ public class PathFilter extends TreeFilter {
 
 	private PathFilter(final String s) {
 		pathStr = s;
-		try {
-			pathRaw = pathStr.getBytes(Constants.CHARACTER_ENCODING);
-		} catch (UnsupportedEncodingException uee) {
-			throw new RuntimeException("JVM doesn't support "
-					+ Constants.CHARACTER_ENCODING
-					+ " which is required for path filtering.", uee);
-		}
+		pathRaw = Constants.encode(pathStr);
 	}
 
 	@Override
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 05/26] Allow AbstractTreeIterators to find out about StopWalkExceptions
  2008-08-12  1:07       ` [EGIT PATCH 04/26] Rely upon Constants.CHARSET over Constants.CHARACTER_ENCODING Shawn O. Pearce
@ 2008-08-12  1:07         ` Shawn O. Pearce
  2008-08-12  1:07           ` [EGIT PATCH 06/26] Implement a new .git/index (aka dircache) read interface Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

If a tree iterator has overridden skip() to specially handle entries
which the TreeWalk has decided are not relevant then it might also
need to pay attention to a StopWalkException.  The SWE is thrown out
of a filter and behaves much like skip, except it skips everything
else in the tree.  This is used by PathFilterGroup to break out of
a walk once we know that no additional records could ever match.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../jgit/treewalk/AbstractTreeIterator.java        |   11 +++++++++++
 .../spearce/jgit/treewalk/EmptyTreeIterator.java   |    6 ++++++
 .../src/org/spearce/jgit/treewalk/TreeWalk.java    |    2 ++
 3 files changed, 19 insertions(+), 0 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/AbstractTreeIterator.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/AbstractTreeIterator.java
index 6d7159c..98fec09 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/AbstractTreeIterator.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/AbstractTreeIterator.java
@@ -391,4 +391,15 @@ public abstract class AbstractTreeIterator {
 	public void skip() throws CorruptObjectException {
 		next();
 	}
+
+	/**
+	 * Indicates to the iterator that no more entries will be read.
+	 * <p>
+	 * This is only invoked by TreeWalk when the iteration is aborted early due
+	 * to a {@link org.spearce.jgit.errors.StopWalkException} being thrown from
+	 * within a TreeFilter.
+	 */
+	public void stopWalk() {
+		// Do nothing by default.  Most iterators do not care.
+	}
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/EmptyTreeIterator.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/EmptyTreeIterator.java
index 09d2bde..c5dc4ad 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/EmptyTreeIterator.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/EmptyTreeIterator.java
@@ -87,4 +87,10 @@ public class EmptyTreeIterator extends AbstractTreeIterator {
 	public void next() throws CorruptObjectException {
 		// Do nothing.
 	}
+
+	@Override
+	public void stopWalk() {
+		if (parent != null)
+			parent.stopWalk();
+	}
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
index d4050ec..1d842bc 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java
@@ -408,6 +408,8 @@ public class TreeWalk {
 				return true;
 			}
 		} catch (StopWalkException stop) {
+			for (final AbstractTreeIterator t : trees)
+				t.stopWalk();
 			return false;
 		}
 	}
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 06/26] Implement a new .git/index (aka dircache) read interface
  2008-08-12  1:07         ` [EGIT PATCH 05/26] Allow AbstractTreeIterators to find out about StopWalkExceptions Shawn O. Pearce
@ 2008-08-12  1:07           ` Shawn O. Pearce
  2008-08-12  1:07             ` [EGIT PATCH 07/26] Export the new DirCache API to Eclipse plugins using jgit Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

This is a smaller (and faster) .git/index file reader and it will be
an eventual replacement for the current GitIndex class.

We silently skip optional extensions, which means we silently skip the
current 'TREE' extension.  We also fail out if we identify a required
extension which we do not support.

Most of each index entry is held in a shared byte[] as we do not need
most of the fields in jgit, due to Java's lack of a POSIX lstat like
utility.  This makes it more efficient for us to read the C Git index
format as we can avoid expending a lot of CPU on decode calls during
the file read.

Lookup is handled by binary searching against the encoded paths, as
this lines up better with how TreeWalk and its TreeFilters operate.
Most of the time when we are looking for something in the index it
is a quick read-find+update-write cycle affecting only a handful of
paths.  Avoiding any unnecessary byte[]->String->byte[] translations
during these operations performance.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../src/org/spearce/jgit/dircache/DirCache.java    |  385 ++++++++++++++++++++
 .../org/spearce/jgit/dircache/DirCacheEntry.java   |  270 ++++++++++++++
 2 files changed, 655 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
new file mode 100644
index 0000000..f8ca60d
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Comparator;
+
+import org.spearce.jgit.errors.CorruptObjectException;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.util.NB;
+
+/**
+ * Support for the Git dircache (aka index file).
+ * <p>
+ * The index file keeps track of which objects are currently checked out in the
+ * working directory, and the last modified time of those working files. Changes
+ * in the working directory can be detected by comparing the modification times
+ * to the cached modification time within the index file.
+ * <p>
+ * Index files are also used during merges, where the merge happens within the
+ * index file first, and the working directory is updated as a post-merge step.
+ * Conflicts are stored in the index file to allow tool (and human) based
+ * resolutions to be easily performed.
+ */
+public class DirCache {
+	private static final byte[] SIG_DIRC = { 'D', 'I', 'R', 'C' };
+
+	private static final int INFO_LEN = DirCacheEntry.INFO_LEN;
+
+	private static final DirCacheEntry[] NO_ENTRIES = {};
+
+	static final Comparator<DirCacheEntry> ENT_CMP = new Comparator<DirCacheEntry>() {
+		public int compare(final DirCacheEntry o1, final DirCacheEntry o2) {
+			final int cr = cmp(o1, o2);
+			if (cr != 0)
+				return cr;
+			return o1.getStage() - o2.getStage();
+		}
+	};
+
+	static int cmp(final DirCacheEntry a, final DirCacheEntry b) {
+		return cmp(a.path, a.path.length, b);
+	}
+
+	static int cmp(final byte[] aPath, final int aLen, final DirCacheEntry b) {
+		return cmp(aPath, aLen, b.path, b.path.length);
+	}
+
+	static int cmp(final byte[] aPath, final int aLen, final byte[] bPath,
+			final int bLen) {
+		for (int cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
+			final int cmp = (aPath[cPos] & 0xff) - (bPath[cPos] & 0xff);
+			if (cmp != 0)
+				return cmp;
+		}
+		return aLen - bLen;
+	}
+
+	/**
+	 * Create a new in-core index representation and read an index from disk.
+	 * <p>
+	 * The new index will be read before it is returned to the caller. Read
+	 * failures are reported as exceptions and therefore prevent the method from
+	 * returning a partially populated index.
+	 * 
+	 * @param indexLocation
+	 *            location of the index file on disk.
+	 * @return a cache representing the contents of the specified index file (if
+	 *         it exists) or an empty cache if the file does not exist.
+	 * @throws IOException
+	 *             the index file is present but could not be read.
+	 * @throws CorruptObjectException
+	 *             the index file is using a format or extension that this
+	 *             library does not support.
+	 */
+	public static DirCache read(final File indexLocation)
+			throws CorruptObjectException, IOException {
+		final DirCache c = new DirCache(indexLocation);
+		c.read();
+		return c;
+	}
+
+	/**
+	 * Create a new in-core index representation and read an index from disk.
+	 * <p>
+	 * The new index will be read before it is returned to the caller. Read
+	 * failures are reported as exceptions and therefore prevent the method from
+	 * returning a partially populated index.
+	 * 
+	 * @param db
+	 *            repository the caller wants to read the default index of.
+	 * @return a cache representing the contents of the specified index file (if
+	 *         it exists) or an empty cache if the file does not exist.
+	 * @throws IOException
+	 *             the index file is present but could not be read.
+	 * @throws CorruptObjectException
+	 *             the index file is using a format or extension that this
+	 *             library does not support.
+	 */
+	public static DirCache read(final Repository db)
+			throws CorruptObjectException, IOException {
+		return read(new File(db.getDirectory(), "index"));
+	}
+
+	/** Location of the current version of the index file. */
+	private final File liveFile;
+
+	/** Modification time of the file at the last read/write we did. */
+	private long lastModified;
+
+	/** Individual file index entries, sorted by path name. */
+	private DirCacheEntry[] sortedEntries;
+
+	/** Number of positions within {@link #sortedEntries} that are valid. */
+	private int entryCnt;
+
+	/**
+	 * Create a new in-core index representation.
+	 * <p>
+	 * The new index will be empty. Callers may wish to read from the on disk
+	 * file first with {@link #read()}.
+	 * 
+	 * @param indexLocation
+	 *            location of the index file on disk.
+	 */
+	public DirCache(final File indexLocation) {
+		liveFile = indexLocation;
+		clear();
+	}
+
+	/**
+	 * Read the index from disk, if it has changed on disk.
+	 * <p>
+	 * This method tries to avoid loading the index if it has not changed since
+	 * the last time we consulted it. A missing index file will be treated as
+	 * though it were present but had no file entries in it.
+	 * 
+	 * @throws IOException
+	 *             the index file is present but could not be read. This
+	 *             DirCache instance may not be populated correctly.
+	 * @throws CorruptObjectException
+	 *             the index file is using a format or extension that this
+	 *             library does not support.
+	 */
+	public void read() throws IOException, CorruptObjectException {
+		if (!liveFile.exists())
+			clear();
+		else if (liveFile.lastModified() != lastModified) {
+			try {
+				final FileInputStream inStream = new FileInputStream(liveFile);
+				try {
+					clear();
+					readFrom(inStream);
+				} finally {
+					try {
+						inStream.close();
+					} catch (IOException err2) {
+						// Ignore any close failures.
+					}
+				}
+			} catch (FileNotFoundException fnfe) {
+				// Someone must have deleted it between our exists test
+				// and actually opening the path. That's fine, its empty.
+				//
+				clear();
+			}
+		}
+	}
+
+	/** Empty this index, removing all entries. */
+	public void clear() {
+		lastModified = 0;
+		sortedEntries = NO_ENTRIES;
+		entryCnt = 0;
+	}
+
+	private void readFrom(final FileInputStream inStream) throws IOException,
+			CorruptObjectException {
+		final FileChannel fd = inStream.getChannel();
+		final long sizeOnDisk = fd.size();
+		final BufferedInputStream in = new BufferedInputStream(inStream);
+
+		// Read the index header and verify we understand it.
+		//
+		final byte[] hdr = new byte[12];
+		NB.readFully(in, hdr, 0, 12);
+		if (!is_DIRC(hdr))
+			throw new CorruptObjectException("Not a DIRC file.");
+		final int ver = NB.decodeInt32(hdr, 4);
+		if (ver != 2)
+			throw new CorruptObjectException("Unknown DIRC version " + ver);
+		entryCnt = NB.decodeInt32(hdr, 8);
+		if (entryCnt < 0)
+			throw new CorruptObjectException("DIRC has too many entries.");
+
+		// Load the individual file entries.
+		//
+		final byte[] infos = new byte[INFO_LEN * entryCnt];
+		sortedEntries = new DirCacheEntry[entryCnt];
+		for (int i = 0; i < entryCnt; i++)
+			sortedEntries[i] = new DirCacheEntry(infos, i * INFO_LEN, in);
+		lastModified = liveFile.lastModified();
+
+		// After the file entries are index extensions.
+		//
+		while (fd.position() - in.available() < sizeOnDisk - 20) {
+			NB.readFully(in, hdr, 0, 8);
+			switch (NB.decodeInt32(hdr, 0)) {
+			default:
+				if (hdr[0] >= 'A' && hdr[0] <= 'Z') {
+					// The extension is optional and is here only as
+					// a performance optimization. Since we do not
+					// understand it, we can safely skip past it.
+					//
+					in.skip(NB.decodeInt32(hdr, 4));
+				} else {
+					// The extension is not an optimization and is
+					// _required_ to understand this index format.
+					// Since we did not trap it above we must abort.
+					//
+					throw new CorruptObjectException("DIRC extension '"
+							+ Constants.CHARSET.decode(
+									ByteBuffer.wrap(hdr, 0, 4)).toString()
+							+ "' not supported by this version.");
+				}
+			}
+		}
+	}
+
+	private static boolean is_DIRC(final byte[] hdr) {
+		if (hdr.length < SIG_DIRC.length)
+			return false;
+		for (int i = 0; i < SIG_DIRC.length; i++)
+			if (hdr[i] != SIG_DIRC[i])
+				return false;
+		return true;
+	}
+
+	/**
+	 * Locate the position a path's entry is at in the index.
+	 * <p>
+	 * If there is at least one entry in the index for this path the position of
+	 * the lowest stage is returned. Subsequent stages can be identified by
+	 * testing consecutive entries until the path differs.
+	 * <p>
+	 * If no path matches the entry -(position+1) is returned, where position is
+	 * the location it would have gone within the index.
+	 * 
+	 * @param path
+	 *            the path to search for.
+	 * @return if >= 0 then the return value is the position of the entry in the
+	 *         index; pass to {@link #getEntry(int)} to obtain the entry
+	 *         information. If < 0 the entry does not exist in the index.
+	 */
+	public int findEntry(final String path) {
+		if (entryCnt == 0)
+			return -1;
+		final byte[] p = Constants.encode(path);
+		return findEntry(p, p.length);
+	}
+
+	int findEntry(final byte[] p, final int pLen) {
+		int low = 0;
+		int high = entryCnt;
+		do {
+			int mid = (low + high) >> 1;
+			final int cmp = cmp(p, pLen, sortedEntries[mid]);
+			if (cmp < 0)
+				high = mid;
+			else if (cmp == 0) {
+				while (mid > 0 && cmp(p, pLen, sortedEntries[mid - 1]) == 0)
+					mid--;
+				return mid;
+			} else
+				low = mid + 1;
+		} while (low < high);
+		return -(low + 1);
+	}
+
+	/**
+	 * Determine the next index position past all entries with the same name.
+	 * <p>
+	 * As index entries are sorted by path name, then stage number, this method
+	 * advances the supplied position to the first position in the index whose
+	 * path name does not match the path name of the supplied position's entry.
+	 * 
+	 * @param position
+	 *            entry position of the path that should be skipped.
+	 * @return position of the next entry whose path is after the input.
+	 */
+	public int nextEntry(final int position) {
+		DirCacheEntry last = sortedEntries[position];
+		int nextIdx = position + 1;
+		while (nextIdx < entryCnt) {
+			final DirCacheEntry next = sortedEntries[nextIdx];
+			if (cmp(last, next) != 0)
+				break;
+			last = next;
+			nextIdx++;
+		}
+		return nextIdx;
+	}
+
+	/**
+	 * Total number of file entries stored in the index.
+	 * <p>
+	 * This count includes unmerged stages for a file entry if the file is
+	 * currently conflicted in a merge. This means the total number of entries
+	 * in the index may be up to 3 times larger than the number of files in the
+	 * working directory.
+	 * <p>
+	 * Note that this value counts only <i>files</i>.
+	 * 
+	 * @return number of entries available.
+	 * @see #getEntry(int)
+	 */
+	public int getEntryCount() {
+		return entryCnt;
+	}
+
+	/**
+	 * Get a specific entry.
+	 * 
+	 * @param i
+	 *            position of the entry to get.
+	 * @return the entry at position <code>i</code>.
+	 */
+	public DirCacheEntry getEntry(final int i) {
+		return sortedEntries[i];
+	}
+
+	/**
+	 * Get a specific entry.
+	 * 
+	 * @param path
+	 *            the path to search for.
+	 * @return the entry at position <code>i</code>.
+	 */
+	public DirCacheEntry getEntry(final String path) {
+		final int i = findEntry(path);
+		return i < 0 ? null : sortedEntries[i];
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
new file mode 100644
index 0000000..7efab48
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.FileMode;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.util.NB;
+
+/**
+ * A single file (or stage of a file) in a {@link DirCache}.
+ * <p>
+ * An entry represents exactly one stage of a file. If a file path is unmerged
+ * then multiple DirCacheEntry instances may appear for the same path name.
+ */
+public class DirCacheEntry {
+	// private static final int P_CTIME = 0;
+
+	// private static final int P_CTIME_NSEC = 4;
+
+	private static final int P_MTIME = 8;
+
+	// private static final int P_MTIME_NSEC = 12;
+
+	// private static final int P_DEV = 16;
+
+	// private static final int P_INO = 20;
+
+	private static final int P_MODE = 24;
+
+	// private static final int P_UID = 28;
+
+	// private static final int P_GID = 32;
+
+	private static final int P_SIZE = 36;
+
+	private static final int P_OBJECTID = 40;
+
+	private static final int P_FLAGS = 60;
+
+	static final int INFO_LEN = 62;
+
+	private static final int ASSUME_VALID = 0x80;
+
+	/** (Possibly shared) header information storage. */
+	private final byte[] info;
+
+	/** First location within {@link #info} where our header starts. */
+	private final int infoOffset;
+
+	/** Our encoded path name, from the root of the repository. */
+	final byte[] path;
+
+	DirCacheEntry(final byte[] sharedInfo, final int infoAt,
+			final InputStream in) throws IOException {
+		info = sharedInfo;
+		infoOffset = infoAt;
+
+		NB.readFully(in, info, infoOffset, INFO_LEN);
+
+		int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & 0xfff;
+		path = new byte[pathLen];
+		NB.readFully(in, path, 0, pathLen);
+
+		// Index records are padded out to the next 8 byte alignment
+		// for historical reasons related to how C Git read the files.
+		//
+		final int actLen = INFO_LEN + pathLen;
+		final int expLen = (actLen + 8) & ~7;
+		if (actLen != expLen)
+			in.skip(expLen - actLen);
+	}
+
+	final byte[] idBuffer() {
+		return info;
+	}
+
+	final int idOffset() {
+		return infoOffset + P_OBJECTID;
+	}
+
+	/**
+	 * Is this entry always thought to be unmodified?
+	 * <p>
+	 * Most entries in the index do not have this flag set. Users may however
+	 * set them on if the file system stat() costs are too high on this working
+	 * directory, such as on NFS or SMB volumes.
+	 * 
+	 * @return true if we must assume the entry is unmodified.
+	 */
+	public boolean isAssumeValid() {
+		return (info[infoOffset + P_FLAGS] & ASSUME_VALID) != 0;
+	}
+
+	/**
+	 * Set the assume valid flag for this entry,
+	 * 
+	 * @param assume
+	 *            true to ignore apparent modifications; false to look at last
+	 *            modified to detect file modifications.
+	 */
+	public void setAssumeValid(final boolean assume) {
+		if (assume)
+			info[infoOffset + P_FLAGS] |= ASSUME_VALID;
+		else
+			info[infoOffset + P_FLAGS] &= ~ASSUME_VALID;
+	}
+
+	/**
+	 * Get the stage of this entry.
+	 * <p>
+	 * Entries have one of 4 possible stages: 0-3.
+	 * 
+	 * @return the stage of this entry.
+	 */
+	public int getStage() {
+		return (info[infoOffset + P_FLAGS] >>> 4) & 0x3;
+	}
+
+	/**
+	 * Obtain the raw {@link FileMode} bits for this entry.
+	 * 
+	 * @return mode bits for the entry.
+	 * @see FileMode#fromBits(int)
+	 */
+	public int getRawMode() {
+		return NB.decodeInt32(info, infoOffset + P_MODE);
+	}
+
+	/**
+	 * Set the file mode for this entry.
+	 * 
+	 * @param mode
+	 *            the new mode constant.
+	 */
+	public void setFileMode(final FileMode mode) {
+		NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits());
+	}
+
+	/**
+	 * Get the cached last modification date of this file, in milliseconds.
+	 * <p>
+	 * One of the indicators that the file has been modified by an application
+	 * changing the working tree is if the last modification time for the file
+	 * differs from the time stored in this entry.
+	 * 
+	 * @return last modification time of this file, in milliseconds since the
+	 *         Java epoch (midnight Jan 1, 1970 UTC).
+	 */
+	public long getLastModified() {
+		return decodeTS(P_MTIME);
+	}
+
+	/**
+	 * Set the cached last modification date of this file, using milliseconds.
+	 * 
+	 * @param when
+	 *            new cached modification date of the file, in milliseconds.
+	 */
+	public void setLastModified(final long when) {
+		encodeTS(P_MTIME, when);
+	}
+
+	/**
+	 * Get the cached size (in bytes) of this file.
+	 * <p>
+	 * One of the indicators that the file has been modified by an application
+	 * changing the working tree is if the size of the file (in bytes) differs
+	 * from the size stored in this entry.
+	 * <p>
+	 * Note that this is the length of the file in the working directory, which
+	 * may differ from the size of the decompressed blob if work tree filters
+	 * are being used, such as LF<->CRLF conversion.
+	 * 
+	 * @return cached size of the working directory file, in bytes.
+	 */
+	public int getLength() {
+		return NB.decodeInt32(info, infoOffset + P_SIZE);
+	}
+
+	/**
+	 * Set the cached size (in bytes) of this file.
+	 * 
+	 * @param sz
+	 *            new cached size of the file, as bytes.
+	 */
+	public void setLength(final int sz) {
+		NB.encodeInt32(info, infoOffset + P_SIZE, sz);
+	}
+
+	/**
+	 * Obtain the ObjectId for the entry.
+	 * <p>
+	 * Using this method to compare ObjectId values between entries is
+	 * inefficient as it causes memory allocation.
+	 * 
+	 * @return object identifier for the entry.
+	 */
+	public ObjectId getObjectId() {
+		return ObjectId.fromRaw(idBuffer(), idOffset());
+	}
+
+	/**
+	 * Get the entry's complete path.
+	 * <p>
+	 * This method is not very efficient and is primarily meant for debugging
+	 * and final output generation. Applications should try to avoid calling it,
+	 * and if invoked do so only once per interesting entry, where the name is
+	 * absolutely required for correct function.
+	 * 
+	 * @return complete path of the entry, from the root of the repository. If
+	 *         the entry is in a subtree there will be at least one '/' in the
+	 *         returned string.
+	 */
+	public String getPathString() {
+		return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
+	}
+
+	private long decodeTS(final int pIdx) {
+		final int base = infoOffset + pIdx;
+		final int sec = NB.decodeInt32(info, base);
+		final int ms = NB.decodeInt32(info, base + 4) / 1000000;
+		return 1000L * sec + ms;
+	}
+
+	private void encodeTS(final int pIdx, final long when) {
+		final int base = infoOffset + pIdx;
+		NB.encodeInt32(info, base, (int) (when / 1000));
+		NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);
+	}
+}
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 07/26] Export the new DirCache API to Eclipse plugins using jgit
  2008-08-12  1:07           ` [EGIT PATCH 06/26] Implement a new .git/index (aka dircache) read interface Shawn O. Pearce
@ 2008-08-12  1:07             ` Shawn O. Pearce
  2008-08-12  1:07               ` [EGIT PATCH 08/26] Support locking (and unlocking) a .git/index through DirCache Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 org.spearce.jgit/META-INF/MANIFEST.MF |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)

diff --git a/org.spearce.jgit/META-INF/MANIFEST.MF b/org.spearce.jgit/META-INF/MANIFEST.MF
index 388dc18..36f92f2 100644
--- a/org.spearce.jgit/META-INF/MANIFEST.MF
+++ b/org.spearce.jgit/META-INF/MANIFEST.MF
@@ -5,7 +5,8 @@ Bundle-SymbolicName: org.spearce.jgit
 Bundle-Version: 0.3.1.qualifier
 Bundle-Localization: plugin
 Bundle-Vendor: %provider_name
-Export-Package: org.spearce.jgit.errors;uses:="org.spearce.jgit.lib",
+Export-Package: org.spearce.jgit.dircache,
+ org.spearce.jgit.errors;uses:="org.spearce.jgit.lib",
  org.spearce.jgit.lib,
  org.spearce.jgit.revplot,
  org.spearce.jgit.revwalk,
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 08/26] Support locking (and unlocking) a .git/index through DirCache
  2008-08-12  1:07             ` [EGIT PATCH 07/26] Export the new DirCache API to Eclipse plugins using jgit Shawn O. Pearce
@ 2008-08-12  1:07               ` Shawn O. Pearce
  2008-08-12  1:07                 ` [EGIT PATCH 09/26] Support writing " Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

In order to safely perform an update of the .git/index file (or any
other DirCache file) we need to lock the file prior to reading its
contents into memory.  This ensures that the contents will not be
modified between the time we start processing the entires and when
the update operation is completed.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../src/org/spearce/jgit/dircache/DirCache.java    |   86 ++++++++++++++++++++
 1 files changed, 86 insertions(+), 0 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
index f8ca60d..8f38a79 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
@@ -48,6 +48,7 @@ import java.util.Comparator;
 
 import org.spearce.jgit.errors.CorruptObjectException;
 import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.LockFile;
 import org.spearce.jgit.lib.Repository;
 import org.spearce.jgit.util.NB;
 
@@ -144,6 +145,56 @@ public class DirCache {
 		return read(new File(db.getDirectory(), "index"));
 	}
 
+	/**
+	 * Create a new in-core index representation, lock it, and read from disk.
+	 * <p>
+	 * The new index will be locked and then read before it is returned to the
+	 * caller. Read failures are reported as exceptions and therefore prevent
+	 * the method from returning a partially populated index.
+	 * 
+	 * @param indexLocation
+	 *            location of the index file on disk.
+	 * @return a cache representing the contents of the specified index file (if
+	 *         it exists) or an empty cache if the file does not exist.
+	 * @throws IOException
+	 *             the index file is present but could not be read, or the lock
+	 *             could not be obtained.
+	 * @throws CorruptObjectException
+	 *             the index file is using a format or extension that this
+	 *             library does not support.
+	 */
+	public static DirCache lock(final File indexLocation)
+			throws CorruptObjectException, IOException {
+		final DirCache c = new DirCache(indexLocation);
+		if (!c.lock())
+			throw new IOException("Cannot lock " + indexLocation);
+		c.read();
+		return c;
+	}
+
+	/**
+	 * Create a new in-core index representation, lock it, and read from disk.
+	 * <p>
+	 * The new index will be locked and then read before it is returned to the
+	 * caller. Read failures are reported as exceptions and therefore prevent
+	 * the method from returning a partially populated index.
+	 * 
+	 * @param db
+	 *            repository the caller wants to read the default index of.
+	 * @return a cache representing the contents of the specified index file (if
+	 *         it exists) or an empty cache if the file does not exist.
+	 * @throws IOException
+	 *             the index file is present but could not be read, or the lock
+	 *             could not be obtained.
+	 * @throws CorruptObjectException
+	 *             the index file is using a format or extension that this
+	 *             library does not support.
+	 */
+	public static DirCache lock(final Repository db)
+			throws CorruptObjectException, IOException {
+		return lock(new File(db.getDirectory(), "index"));
+	}
+
 	/** Location of the current version of the index file. */
 	private final File liveFile;
 
@@ -156,6 +207,9 @@ public class DirCache {
 	/** Number of positions within {@link #sortedEntries} that are valid. */
 	private int entryCnt;
 
+	/** Our active lock (if we hold it); null if we don't have it locked. */
+	private LockFile myLock;
+
 	/**
 	 * Create a new in-core index representation.
 	 * <p>
@@ -279,6 +333,38 @@ public class DirCache {
 	}
 
 	/**
+	 * Try to establish an update lock on the cache file.
+	 * 
+	 * @return true if the lock is now held by the caller; false if it is held
+	 *         by someone else.
+	 * @throws IOException
+	 *             the output file could not be created. The caller does not
+	 *             hold the lock.
+	 */
+	public boolean lock() throws IOException {
+		final LockFile tmp = new LockFile(liveFile);
+		if (tmp.lock()) {
+			tmp.setNeedStatInformation(true);
+			myLock = tmp;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Unlock this file and abort this change.
+	 * <p>
+	 * The temporary file (if created) is deleted before returning.
+	 */
+	public void unlock() {
+		final LockFile tmp = myLock;
+		if (tmp != null) {
+			myLock = null;
+			tmp.unlock();
+		}
+	}
+
+	/**
 	 * Locate the position a path's entry is at in the index.
 	 * <p>
 	 * If there is at least one entry in the index for this path the position of
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 09/26] Support writing a .git/index through DirCache
  2008-08-12  1:07               ` [EGIT PATCH 08/26] Support locking (and unlocking) a .git/index through DirCache Shawn O. Pearce
@ 2008-08-12  1:07                 ` Shawn O. Pearce
  2008-08-12  1:07                   ` [EGIT PATCH 10/26] Support the 'TREE' extension in " Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

We now support writing back the same entires we read in, with maybe
some minor entry level edits such as to change the mode of an entry,
or the assume valid flag.

We also include full support for smudging potentially 'racily clean'
entries while writing an updated index file back to disk.  We differ
from C Git in how we smudge an entry, but the effect should be the
same in that potentially racily clean entries will require C Git to
compare the content of the file, at least until Jan 19, 2038.  The
smudge rule needs to be different because we do not have access to
the working directory in order to validate a racily clean entry.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../src/org/spearce/jgit/dircache/DirCache.java    |   97 ++++++++++++++++++++
 .../org/spearce/jgit/dircache/DirCacheEntry.java   |   75 +++++++++++++++
 2 files changed, 172 insertions(+), 0 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
index 8f38a79..248e2a2 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
@@ -38,12 +38,16 @@
 package org.spearce.jgit.dircache;
 
 import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
 import java.util.Comparator;
 
 import org.spearce.jgit.errors.CorruptObjectException;
@@ -352,6 +356,99 @@ public class DirCache {
 	}
 
 	/**
+	 * Write the entry records from memory to disk.
+	 * <p>
+	 * The cache must be locked first by calling {@link #lock()} and receiving
+	 * true as the return value. Applications are encouraged to lock the index,
+	 * then invoke {@link #read()} to ensure the in-memory data is current,
+	 * prior to updating the in-memory entries.
+	 * <p>
+	 * Once written the lock is closed and must be either committed with
+	 * {@link #commit()} or rolled back with {@link #unlock()}.
+	 * 
+	 * @throws IOException
+	 *             the output file could not be created. The caller no longer
+	 *             holds the lock.
+	 */
+	public void write() throws IOException {
+		final LockFile tmp = myLock;
+		requireLocked(tmp);
+		try {
+			writeTo(new BufferedOutputStream(tmp.getOutputStream()));
+		} catch (IOException err) {
+			tmp.unlock();
+			throw err;
+		} catch (RuntimeException err) {
+			tmp.unlock();
+			throw err;
+		} catch (Error err) {
+			tmp.unlock();
+			throw err;
+		}
+	}
+
+	private void writeTo(final OutputStream os) throws IOException {
+		final MessageDigest foot = Constants.newMessageDigest();
+		final DigestOutputStream dos = new DigestOutputStream(os, foot);
+
+		// Write the header.
+		//
+		final byte[] tmp = new byte[128];
+		System.arraycopy(SIG_DIRC, 0, tmp, 0, SIG_DIRC.length);
+		NB.encodeInt32(tmp, 4, /* version */2);
+		NB.encodeInt32(tmp, 8, entryCnt);
+		dos.write(tmp, 0, 12);
+
+		// Write the individual file entries.
+		//
+		if (lastModified <= 0) {
+			// Write a new index, as no entries require smudging.
+			//
+			for (int i = 0; i < entryCnt; i++)
+				sortedEntries[i].write(dos);
+		} else {
+			final int smudge_s = (int) (lastModified / 1000);
+			final int smudge_ns = ((int) (lastModified % 1000)) * 1000000;
+			for (int i = 0; i < entryCnt; i++) {
+				final DirCacheEntry e = sortedEntries[i];
+				if (e.mightBeRacilyClean(smudge_s, smudge_ns))
+					e.smudgeRacilyClean();
+				e.write(dos);
+			}
+		}
+
+		os.write(foot.digest());
+		os.close();
+	}
+
+	/**
+	 * Commit this change and release the lock.
+	 * <p>
+	 * If this method fails (returns false) the lock is still released.
+	 * 
+	 * @return true if the commit was successful and the file contains the new
+	 *         data; false if the commit failed and the file remains with the
+	 *         old data.
+	 * @throws IllegalStateException
+	 *             the lock is not held.
+	 */
+	public boolean commit() {
+		final LockFile tmp = myLock;
+		requireLocked(tmp);
+		myLock = null;
+		if (!tmp.commit())
+			return false;
+		lastModified = tmp.getCommitLastModified();
+		return true;
+	}
+
+	private void requireLocked(final LockFile tmp) {
+		if (tmp == null)
+			throw new IllegalStateException("DirCache "
+					+ liveFile.getAbsolutePath() + " not locked.");
+	}
+
+	/**
 	 * Unlock this file and abort this change.
 	 * <p>
 	 * The temporary file (if created) is deleted before returning.
diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
index 7efab48..8ca8f22 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
@@ -39,7 +39,9 @@ package org.spearce.jgit.dircache;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 
 import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.FileMode;
@@ -53,6 +55,8 @@ import org.spearce.jgit.util.NB;
  * then multiple DirCacheEntry instances may appear for the same path name.
  */
 public class DirCacheEntry {
+	private static final byte[] nullpad = new byte[8];
+
 	// private static final int P_CTIME = 0;
 
 	// private static final int P_CTIME_NSEC = 4;
@@ -110,6 +114,77 @@ public class DirCacheEntry {
 			in.skip(expLen - actLen);
 	}
 
+	void write(final OutputStream os) throws IOException {
+		final int pathLen = path.length;
+		os.write(info, infoOffset, INFO_LEN);
+		os.write(path, 0, pathLen);
+
+		// Index records are padded out to the next 8 byte alignment
+		// for historical reasons related to how C Git read the files.
+		//
+		final int actLen = INFO_LEN + pathLen;
+		final int expLen = (actLen + 8) & ~7;
+		if (actLen != expLen)
+			os.write(nullpad, 0, expLen - actLen);
+	}
+
+	/**
+	 * Is it possible for this entry to be accidently assumed clean?
+	 * <p>
+	 * The "racy git" problem happens when a work file can be updated faster
+	 * than the filesystem records file modification timestamps. It is possible
+	 * for an application to edit a work file, update the index, then edit it
+	 * again before the filesystem will give the work file a new modification
+	 * timestamp. This method tests to see if file was written out at the same
+	 * time as the index.
+	 * 
+	 * @param smudge_s
+	 *            seconds component of the index's last modified time.
+	 * @param smudge_ns
+	 *            nanoseconds component of the index's last modified time.
+	 * @return true if extra careful checks should be used.
+	 */
+	final boolean mightBeRacilyClean(final int smudge_s, final int smudge_ns) {
+		// If the index has a modification time then it came from disk
+		// and was not generated from scratch in memory. In such cases
+		// the entry is 'racily clean' if the entry's cached modification
+		// time is equal to or later than the index modification time. In
+		// such cases the work file is too close to the index to tell if
+		// it is clean or not based on the modification time alone.
+		//
+		final int base = infoOffset + P_MTIME;
+		final int mtime = NB.decodeInt32(info, base);
+		if (smudge_s < mtime)
+			return true;
+		if (smudge_s == mtime)
+			return smudge_ns <= NB.decodeInt32(info, base + 4) / 1000000;
+		return false;
+	}
+
+	/**
+	 * Force this entry to no longer match its working tree file.
+	 * <p>
+	 * This avoids the "racy git" problem by making this index entry no longer
+	 * match the file in the working directory. Later git will be forced to
+	 * compare the file content to ensure the file matches the working tree.
+	 */
+	final void smudgeRacilyClean() {
+		// We don't use the same approach as C Git to smudge the entry,
+		// as we cannot compare the working tree file to our SHA-1 and
+		// thus cannot use the "size to 0" trick without accidentally
+		// thinking a zero length file is clean.
+		//
+		// Instead we force the mtime to the largest possible value, so
+		// it is certainly after the index's own modification time and
+		// on a future read will cause mightBeRacilyClean to say "yes!".
+		// It is also unlikely to match with the working tree file.
+		//
+		// I'll see you again before Jan 19, 2038, 03:14:07 AM GMT.
+		//
+		final int base = infoOffset + P_MTIME;
+		Arrays.fill(info, base, base + 8, (byte) 127);
+	}
+
 	final byte[] idBuffer() {
 		return info;
 	}
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 10/26] Support the 'TREE' extension in .git/index through DirCache
  2008-08-12  1:07                 ` [EGIT PATCH 09/26] Support writing " Shawn O. Pearce
@ 2008-08-12  1:07                   ` Shawn O. Pearce
  2008-08-12  1:07                     ` [EGIT PATCH 11/26] Support using a DirCache within a TreeWalk Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

We now support reading and writing the 'TREE' extension of the 'DIRC'
file format.  C Git uses this as an optional extension to cache the
SHA-1 id of each tree object used by the index, permitting C Git to
identify subsections of the repository which have not been modified
and to speed up the git-write-tree plumbing command prior to making
a commit object.

Our support includes constructing the tree objects if they do not
exist, but we don't write them to the object database.  This change
only builds the ranges within the index file and marks them invalid,
so C Git knows that the tree must be generated and saved into the
object database before it can rely upon it in a commit.

Like the DirCacheEntry objects we try to avoid unnecessary encode
and decode operations during reads and writes as they would just
drag down the processor.

Our sort ordering of the 'TREE' data is different from that of C Git.
C Git sorts tree children according to lengths of path names, as the
actual order was not relevant but Junio wanted a stable ordering.  We
want our tree children sorted by name so we can merge-sort them onto
the main index entries.  That means we may have to sort the children
after we read them from disk; at least that is true if C Git was the
last process to write the index file.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../src/org/spearce/jgit/dircache/DirCache.java    |   46 ++
 .../org/spearce/jgit/dircache/DirCacheTree.java    |  453 ++++++++++++++++++++
 2 files changed, 499 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheTree.java

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
index 248e2a2..b5fdd46 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
@@ -54,7 +54,9 @@ import org.spearce.jgit.errors.CorruptObjectException;
 import org.spearce.jgit.lib.Constants;
 import org.spearce.jgit.lib.LockFile;
 import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.util.MutableInteger;
 import org.spearce.jgit.util.NB;
+import org.spearce.jgit.util.TemporaryBuffer;
 
 /**
  * Support for the Git dircache (aka index file).
@@ -72,6 +74,8 @@ import org.spearce.jgit.util.NB;
 public class DirCache {
 	private static final byte[] SIG_DIRC = { 'D', 'I', 'R', 'C' };
 
+	private static final int EXT_TREE = 0x54524545 /* 'TREE' */;
+
 	private static final int INFO_LEN = DirCacheEntry.INFO_LEN;
 
 	private static final DirCacheEntry[] NO_ENTRIES = {};
@@ -211,6 +215,9 @@ public class DirCache {
 	/** Number of positions within {@link #sortedEntries} that are valid. */
 	private int entryCnt;
 
+	/** Cache tree for this index; null if the cache tree is not available. */
+	private DirCacheTree tree;
+
 	/** Our active lock (if we hold it); null if we don't have it locked. */
 	private LockFile myLock;
 
@@ -272,6 +279,7 @@ public class DirCache {
 		lastModified = 0;
 		sortedEntries = NO_ENTRIES;
 		entryCnt = 0;
+		tree = null;
 	}
 
 	private void readFrom(final FileInputStream inStream) throws IOException,
@@ -306,6 +314,12 @@ public class DirCache {
 		while (fd.position() - in.available() < sizeOnDisk - 20) {
 			NB.readFully(in, hdr, 0, 8);
 			switch (NB.decodeInt32(hdr, 0)) {
+			case EXT_TREE: {
+				final byte[] raw = new byte[NB.decodeInt32(hdr, 4)];
+				NB.readFully(in, raw, 0, raw.length);
+				tree = new DirCacheTree(raw, new MutableInteger(), null);
+				break;
+			}
 			default:
 				if (hdr[0] >= 'A' && hdr[0] <= 'Z') {
 					// The extension is optional and is here only as
@@ -417,6 +431,17 @@ public class DirCache {
 			}
 		}
 
+		if (tree != null) {
+			final TemporaryBuffer bb = new TemporaryBuffer();
+			tree.write(tmp, bb);
+			bb.close();
+
+			NB.encodeInt32(tmp, 0, EXT_TREE);
+			NB.encodeInt32(tmp, 4, (int) bb.length());
+			dos.write(tmp, 0, 8);
+			bb.writeTo(dos, null);
+		}
+
 		os.write(foot.digest());
 		os.close();
 	}
@@ -565,4 +590,25 @@ public class DirCache {
 		final int i = findEntry(path);
 		return i < 0 ? null : sortedEntries[i];
 	}
+
+	/**
+	 * Obtain (or build) the current cache tree structure.
+	 * <p>
+	 * This method can optionally recreate the cache tree, without flushing the
+	 * tree objects themselves to disk.
+	 * 
+	 * @param build
+	 *            if true and the cache tree is not present in the index it will
+	 *            be generated and returned to the caller.
+	 * @return the cache tree; null if there is no current cache tree available
+	 *         and <code>build</code> was false.
+	 */
+	public DirCacheTree getCacheTree(final boolean build) {
+		if (build) {
+			if (tree == null)
+				tree = new DirCacheTree();
+			tree.validate(sortedEntries, entryCnt, 0, 0);
+		}
+		return tree;
+	}
 }
diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheTree.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheTree.java
new file mode 100644
index 0000000..d06be08
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheTree.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.util.MutableInteger;
+import org.spearce.jgit.util.RawParseUtils;
+
+/**
+ * Single tree record from the 'TREE' {@link DirCache} extension.
+ * <p>
+ * A valid cache tree record contains the object id of a tree object and the
+ * total number of {@link DirCacheEntry} instances (counted recursively) from
+ * the DirCache contained within the tree. This information facilitates faster
+ * traversal of the index and quicker generation of tree objects prior to
+ * creating a new commit.
+ * <p>
+ * An invalid cache tree record indicates a known subtree whose file entries
+ * have changed in ways that cause the tree to no longer have a known object id.
+ * Invalid cache tree records must be revalidated prior to use.
+ */
+public class DirCacheTree {
+	private static final byte[] NO_NAME = {};
+
+	private static final DirCacheTree[] NO_CHILDREN = {};
+
+	private static final Comparator<DirCacheTree> TREE_CMP = new Comparator<DirCacheTree>() {
+		public int compare(final DirCacheTree o1, final DirCacheTree o2) {
+			final byte[] a = o1.encodedName;
+			final byte[] b = o2.encodedName;
+			final int aLen = a.length;
+			final int bLen = b.length;
+			int cPos;
+			for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
+				final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
+				if (cmp != 0)
+					return cmp;
+			}
+			if (aLen == bLen)
+				return 0;
+			if (aLen < bLen)
+				return '/' - (b[cPos] & 0xff);
+			return (a[cPos] & 0xff) - '/';
+		}
+	};
+
+	/** Tree this tree resides in; null if we are the root. */
+	private DirCacheTree parent;
+
+	/** Name of this tree within its parent. */
+	private byte[] encodedName;
+
+	/** Number of {@link DirCacheEntry} records that belong to this tree. */
+	private int entrySpan;
+
+	/** Unique SHA-1 of this tree; null if invalid. */
+	private ObjectId id;
+
+	/** Child trees, if any, sorted by {@link #encodedName}. */
+	private DirCacheTree[] children;
+
+	/** Number of valid children in {@link #children}. */
+	private int childCnt;
+
+	DirCacheTree() {
+		encodedName = NO_NAME;
+		children = NO_CHILDREN;
+		childCnt = 0;
+		entrySpan = -1;
+	}
+
+	private DirCacheTree(final DirCacheTree myParent, final byte[] path,
+			final int pathOff, final int pathLen) {
+		parent = myParent;
+		encodedName = new byte[pathLen];
+		System.arraycopy(path, pathOff, encodedName, 0, pathLen);
+		children = NO_CHILDREN;
+		childCnt = 0;
+		entrySpan = -1;
+	}
+
+	DirCacheTree(final byte[] in, final MutableInteger off,
+			final DirCacheTree myParent) {
+		parent = myParent;
+
+		int ptr = RawParseUtils.next(in, off.value, '\0');
+		final int nameLen = ptr - off.value - 1;
+		if (nameLen > 0) {
+			encodedName = new byte[nameLen];
+			System.arraycopy(in, off.value, encodedName, 0, nameLen);
+		} else
+			encodedName = NO_NAME;
+
+		entrySpan = RawParseUtils.parseBase10(in, ptr, off);
+		final int subcnt = RawParseUtils.parseBase10(in, off.value, off);
+		off.value = RawParseUtils.next(in, off.value, '\n');
+
+		if (entrySpan >= 0) {
+			// Valid trees have a positive entry count and an id of a
+			// tree object that should exist in the object database.
+			//
+			id = ObjectId.fromRaw(in, off.value);
+			off.value += Constants.OBJECT_ID_LENGTH;
+		}
+
+		if (subcnt > 0) {
+			boolean alreadySorted = true;
+			children = new DirCacheTree[subcnt];
+			for (int i = 0; i < subcnt; i++) {
+				children[i] = new DirCacheTree(in, off, this);
+
+				// C Git's ordering differs from our own; it prefers to
+				// sort by length first. This sometimes produces a sort
+				// we do not desire. On the other hand it may have been
+				// created by us, and be sorted the way we want.
+				//
+				if (alreadySorted && i > 0
+						&& TREE_CMP.compare(children[i - 1], children[i]) > 0)
+					alreadySorted = false;
+			}
+			if (!alreadySorted)
+				Arrays.sort(children, 0, subcnt, TREE_CMP);
+		} else {
+			// Leaf level trees have no children, only (file) entries.
+			//
+			children = NO_CHILDREN;
+		}
+		childCnt = subcnt;
+	}
+
+	void write(final byte[] tmp, final OutputStream os) throws IOException {
+		int ptr = tmp.length;
+		tmp[--ptr] = '\n';
+		ptr = RawParseUtils.formatBase10(tmp, ptr, childCnt);
+		tmp[--ptr] = ' ';
+		ptr = RawParseUtils.formatBase10(tmp, ptr, isValid() ? entrySpan : -1);
+		tmp[--ptr] = 0;
+
+		os.write(encodedName);
+		os.write(tmp, ptr, tmp.length - ptr);
+		if (isValid()) {
+			id.copyRawTo(tmp, 0);
+			os.write(tmp, 0, Constants.OBJECT_ID_LENGTH);
+		}
+		for (int i = 0; i < childCnt; i++)
+			children[i].write(tmp, os);
+	}
+
+	/**
+	 * Determine if this cache is currently valid.
+	 * <p>
+	 * A valid cache tree knows how many {@link DirCacheEntry} instances from
+	 * the parent {@link DirCache} reside within this tree (recursively
+	 * enumerated). It also knows the object id of the tree, as the tree should
+	 * be readily available from the repository's object database.
+	 * 
+	 * @return true if this tree is knows key details about itself; false if the
+	 *         tree needs to be regenerated.
+	 */
+	public boolean isValid() {
+		return id != null;
+	}
+
+	/**
+	 * Get the number of entries this tree spans within the DirCache.
+	 * <p>
+	 * If this tree is not valid (see {@link #isValid()}) this method's return
+	 * value is always strictly negative (less than 0) but is otherwise an
+	 * undefined result.
+	 * 
+	 * @return total number of entries (recursively) contained within this tree.
+	 */
+	public int getEntrySpan() {
+		return entrySpan;
+	}
+
+	/**
+	 * Get the number of cached subtrees contained within this tree.
+	 * 
+	 * @return number of child trees available through this tree.
+	 */
+	public int getChildCount() {
+		return childCnt;
+	}
+
+	/**
+	 * Get the i-th child cache tree.
+	 * 
+	 * @param i
+	 *            index of the child to obtain.
+	 * @return the child tree.
+	 */
+	public DirCacheTree getChild(final int i) {
+		return children[i];
+	}
+
+	ObjectId getObjectId() {
+		return id;
+	}
+
+	/**
+	 * Get the tree's name within its parent.
+	 * <p>
+	 * This method is not very efficient and is primarily meant for debugging
+	 * and final output generation. Applications should try to avoid calling it,
+	 * and if invoked do so only once per interesting entry, where the name is
+	 * absolutely required for correct function.
+	 * 
+	 * @return name of the tree. This does not contain any '/' characters.
+	 */
+	public String getNameString() {
+		final ByteBuffer bb = ByteBuffer.wrap(encodedName);
+		return Constants.CHARSET.decode(bb).toString();
+	}
+
+	/**
+	 * Get the tree's path within the repository.
+	 * <p>
+	 * This method is not very efficient and is primarily meant for debugging
+	 * and final output generation. Applications should try to avoid calling it,
+	 * and if invoked do so only once per interesting entry, where the name is
+	 * absolutely required for correct function.
+	 * 
+	 * @return path of the tree, relative to the repository root. If this is not
+	 *         the root tree the path ends with '/'. The root tree's path string
+	 *         is the empty string ("").
+	 */
+	public String getPathString() {
+		final StringBuilder r = new StringBuilder();
+		appendName(r);
+		return r.toString();
+	}
+
+	private void appendName(final StringBuilder r) {
+		if (parent != null) {
+			parent.appendName(r);
+			r.append(getNameString());
+			r.append('/');
+		} else if (nameLength() > 0) {
+			r.append(getNameString());
+			r.append('/');
+		}
+	}
+
+	final int nameLength() {
+		return encodedName.length;
+	}
+
+	final boolean contains(final byte[] a, int aOff, final int aLen) {
+		final byte[] e = encodedName;
+		final int eLen = e.length;
+		for (int eOff = 0; eOff < eLen && aOff < aLen; eOff++, aOff++)
+			if (e[eOff] != a[aOff])
+				return false;
+		if (aOff == aLen)
+			return false;
+		return a[aOff] == '/';
+	}
+
+	/**
+	 * Update (if necessary) this tree's entrySpan.
+	 * 
+	 * @param cache
+	 *            the complete cache from DirCache.
+	 * @param cCnt
+	 *            number of entries in <code>cache</code> that are valid for
+	 *            iteration.
+	 * @param cIdx
+	 *            first position of <code>cache</code> that is a member of this
+	 *            tree. The path of <code>cache[cacheIdx].path</code> for the
+	 *            range <code>[0,pathOff-1)</code> matches the complete path of
+	 *            this tree, from the root of the repository.
+	 * @param pathOff
+	 *            number of bytes of <code>cache[cacheIdx].path</code> that
+	 *            matches this tree's path. The value at array position
+	 *            <code>cache[cacheIdx].path[pathOff-1]</code> is always '/' if
+	 *            <code>pathOff</code> is > 0.
+	 */
+	void validate(final DirCacheEntry[] cache, final int cCnt, int cIdx,
+			final int pathOff) {
+		if (entrySpan >= 0) {
+			// If we are valid, our children are also valid.
+			// We have no need to validate them.
+			//
+			return;
+		}
+
+		entrySpan = 0;
+		if (cCnt == 0) {
+			// Special case of an empty index, and we are the root tree.
+			//
+			return;
+		}
+
+		final byte[] firstPath = cache[cIdx].path;
+		int stIdx = 0;
+		while (cIdx < cCnt) {
+			final byte[] currPath = cache[cIdx].path;
+			if (pathOff > 0 && !peq(firstPath, currPath, pathOff)) {
+				// The current entry is no longer in this tree. Our
+				// span is updated and the remainder goes elsewhere.
+				//
+				break;
+			}
+
+			DirCacheTree st = stIdx < childCnt ? children[stIdx] : null;
+			final int cc = namecmp(currPath, pathOff, st);
+			if (cc > 0) {
+				// This subtree is now empty.
+				//
+				removeChild(stIdx);
+				continue;
+			}
+
+			if (cc < 0) {
+				final int p = slash(currPath, pathOff);
+				if (p < 0) {
+					// The entry has no '/' and thus is directly in this
+					// tree. Count it as one of our own.
+					//
+					cIdx++;
+					entrySpan++;
+					continue;
+				}
+
+				// Build a new subtree for this entry.
+				//
+				st = new DirCacheTree(this, currPath, pathOff, p - pathOff);
+				insertChild(stIdx, st);
+			}
+
+			// The entry is contained in this subtree.
+			//
+			st.validate(cache, cCnt, cIdx, pathOff + st.nameLength() + 1);
+			cIdx += st.entrySpan;
+			entrySpan += st.entrySpan;
+			stIdx++;
+		}
+
+		if (stIdx < childCnt) {
+			// None of our remaining children can be in this tree
+			// as the current cache entry is after our own name.
+			//
+			final DirCacheTree[] dct = new DirCacheTree[stIdx];
+			System.arraycopy(children, 0, dct, 0, stIdx);
+			children = dct;
+		}
+	}
+
+	private void insertChild(final int stIdx, final DirCacheTree st) {
+		final DirCacheTree[] c = children;
+		if (childCnt + 1 <= c.length) {
+			if (stIdx < childCnt)
+				System.arraycopy(c, stIdx, c, stIdx + 1, childCnt - stIdx);
+			c[stIdx] = st;
+			childCnt++;
+			return;
+		}
+
+		final int n = c.length;
+		final DirCacheTree[] a = new DirCacheTree[n + 1];
+		if (stIdx > 0)
+			System.arraycopy(c, 0, a, 0, stIdx);
+		a[stIdx] = st;
+		if (stIdx < n)
+			System.arraycopy(c, stIdx, a, stIdx + 1, n - stIdx);
+		children = a;
+		childCnt++;
+	}
+
+	private void removeChild(final int stIdx) {
+		final int n = --childCnt;
+		if (stIdx < n)
+			System.arraycopy(children, stIdx + 1, children, stIdx, n - stIdx);
+		children[n] = null;
+	}
+
+	static boolean peq(final byte[] a, final byte[] b, int aLen) {
+		if (b.length < aLen)
+			return false;
+		for (aLen--; aLen >= 0; aLen--)
+			if (a[aLen] != b[aLen])
+				return false;
+		return true;
+	}
+
+	private static int namecmp(final byte[] a, int aPos, final DirCacheTree ct) {
+		if (ct == null)
+			return -1;
+		final byte[] b = ct.encodedName;
+		final int aLen = a.length;
+		final int bLen = b.length;
+		int bPos = 0;
+		for (; aPos < aLen && bPos < bLen; aPos++, bPos++) {
+			final int cmp = (a[aPos] & 0xff) - (b[bPos] & 0xff);
+			if (cmp != 0)
+				return cmp;
+		}
+		if (bPos == bLen)
+			return a[aPos] == '/' ? 0 : -1;
+		return aLen - bLen;
+	}
+
+	private static int slash(final byte[] a, int aPos) {
+		final int aLen = a.length;
+		for (; aPos < aLen; aPos++)
+			if (a[aPos] == '/')
+				return aPos;
+		return -1;
+	}
+}
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 11/26] Support using a DirCache within a TreeWalk
  2008-08-12  1:07                   ` [EGIT PATCH 10/26] Support the 'TREE' extension in " Shawn O. Pearce
@ 2008-08-12  1:07                     ` Shawn O. Pearce
  2008-08-12  1:07                       ` [EGIT PATCH 12/26] Support recreating a .git/index through DirCache Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

Being able to include the .git/index file as part of a parallel TreeWalk
which also covers one or more canonical tree objects from the database
and the working directory makes it much easier to handle differences or
merges between the various states.

This iterator implementation adapts a loaded DirCache instance into a
tree structure that the TreeWalk can iterate.  Since TreeWalk wants to
use a hierarchical structure and the DirCache is flat we force the TREE
extension to load (or generate) and work off that.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../spearce/jgit/dircache/DirCacheIterator.java    |  205 ++++++++++++++++++++
 1 files changed, 205 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheIterator.java

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheIterator.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheIterator.java
new file mode 100644
index 0000000..f7ef15d
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheIterator.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.spearce.jgit.errors.CorruptObjectException;
+import org.spearce.jgit.errors.IncorrectObjectTypeException;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.FileMode;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.treewalk.AbstractTreeIterator;
+
+/**
+ * Iterate a {@link DirCache} as part of a <code>TreeWalk</code>.
+ * <p>
+ * This is an iterator to adapt a loaded <code>DirCache</code> instance (such as
+ * read from an existing <code>.git/index</code> file) to the tree structure
+ * used by a <code>TreeWalk</code>, making it possible for applications to walk
+ * over any combination of tree objects already in the object database, index
+ * files, or working directories.
+ * 
+ * @see org.spearce.jgit.treewalk.TreeWalk
+ */
+public class DirCacheIterator extends AbstractTreeIterator {
+	/** The cache this iterator was created to walk. */
+	protected final DirCache cache;
+
+	/** The tree this iterator is walking. */
+	private final DirCacheTree tree;
+
+	/** Special buffer to hold the ObjectId of {@link #currentSubtree}. */
+	private final byte[] subtreeId;
+
+	/** Index of entry within {@link #cache}. */
+	protected int cachePos;
+
+	/** Position of entry within {@link #tree}'s entry span. */
+	private int treePos;
+
+	/** Next subtree to consider within {@link #tree}. */
+	private int subtreeIdx;
+
+	/** The current file entry from {@link #cache}, matching {@link #cachePos}. */
+	protected DirCacheEntry currentEntry;
+
+	/** The subtree containing {@link #currentEntry} if this is first entry. */
+	protected DirCacheTree currentSubtree;
+
+	/**
+	 * Create a new iterator for an already loaded DirCache instance.
+	 * <p>
+	 * The iterator implementation may copy part of the cache's data during
+	 * construction, so the cache must be read in prior to creating the
+	 * iterator.
+	 * 
+	 * @param dc
+	 *            the cache to walk. It must be already loaded into memory.
+	 */
+	public DirCacheIterator(final DirCache dc) {
+		cache = dc;
+		tree = dc.getCacheTree(true);
+		subtreeId = new byte[Constants.OBJECT_ID_LENGTH];
+		cachePos = -1;
+		treePos = -1;
+	}
+
+	protected DirCacheIterator(final DirCacheIterator p, final DirCacheTree dct) {
+		super(p, p.path, p.pathLen + 1);
+		cache = p.cache;
+		tree = dct;
+		subtreeId = p.subtreeId;
+		cachePos = p.cachePos - 1; // back up so first next() call enters it
+		treePos = -1;
+	}
+
+	@Override
+	public AbstractTreeIterator createSubtreeIterator(final Repository repo)
+			throws IncorrectObjectTypeException, IOException {
+		if (currentSubtree == null)
+			throw new IncorrectObjectTypeException(getEntryObjectId(),
+					Constants.TYPE_TREE);
+		return new DirCacheIterator(this, currentSubtree);
+	}
+
+	@Override
+	public byte[] idBuffer() {
+		if (currentSubtree != null)
+			return subtreeId;
+		if (currentEntry != null)
+			return currentEntry.idBuffer();
+		return zeroid;
+	}
+
+	@Override
+	public int idOffset() {
+		if (currentSubtree != null)
+			return 0;
+		if (currentEntry != null)
+			return currentEntry.idOffset();
+		return 0;
+	}
+
+	@Override
+	public boolean eof() {
+		return treePos >= tree.getEntrySpan();
+	}
+
+	@Override
+	public void next() throws CorruptObjectException {
+		if (currentSubtree != null) {
+			// If our last position was a subtree we need to skip over
+			// its entire span to get to the item after the subtree.
+			//
+			final int n = currentSubtree.getEntrySpan();
+			cachePos += n;
+			treePos += n;
+			currentSubtree = null;
+		} else {
+			// Our last position was a file/symlink/gitlink, so we
+			// only skip the one entry.
+			//
+			cachePos++;
+			treePos++;
+		}
+
+		if (treePos >= tree.getEntrySpan())
+			return; // this iterator is now at EOF.
+
+		currentEntry = cache.getEntry(cachePos);
+		final byte[] cep = currentEntry.path;
+		if (subtreeIdx < tree.getChildCount()) {
+			final DirCacheTree s = tree.getChild(subtreeIdx);
+			if (s.contains(cep, pathOffset, cep.length)) {
+				// The current position is the first file of this subtree.
+				// Use the subtree instead as the current position.
+				//
+				currentSubtree = s;
+				subtreeIdx++;
+
+				if (s.isValid())
+					s.getObjectId().copyRawTo(subtreeId, 0);
+				else
+					Arrays.fill(subtreeId, (byte) 0);
+				mode = FileMode.TREE.getBits();
+				path = cep;
+				pathLen = pathOffset + s.nameLength();
+				return;
+			}
+		}
+
+		// The current position is a file/symlink/gitlink so we
+		// do not have a subtree located here.
+		//
+		mode = currentEntry.getRawMode();
+		path = cep;
+		pathLen = cep.length;
+	}
+
+	/**
+	 * Get the DirCacheEntry for the current file.
+	 * 
+	 * @return the current cache entry, if this iterator is positioned on a
+	 *         non-tree.
+	 */
+	public DirCacheEntry getDirCacheEntry() {
+		return currentSubtree == null ? currentEntry : null;
+	}
+}
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 12/26] Support recreating a .git/index through DirCache
  2008-08-12  1:07                     ` [EGIT PATCH 11/26] Support using a DirCache within a TreeWalk Shawn O. Pearce
@ 2008-08-12  1:07                       ` Shawn O. Pearce
  2008-08-12  1:08                         ` [EGIT PATCH 13/26] Support iterating and building a DirCache at the same time Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:07 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

The DirCacheBuilder is meant for building up a new DirCache content
by iterating through the paths in proper Git sort order.  This is
mostly useful during a TreeWalk, where the paths are being fed to
the application in the correct sort ordering.  Appending them into
the new entry table in order avoids sorting costs later on when we
try to write the file back to disk.

The API is designed for speed, not ease-of-use.  Performing updates
in order and by copying large ranges of the existing index into the
new index is incredibly fast, but requires the application to work
harder to construct the right sequence of calls.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../spearce/jgit/dircache/BaseDirCacheEditor.java  |  194 ++++++++++++++++++++
 .../src/org/spearce/jgit/dircache/DirCache.java    |   23 +++
 .../org/spearce/jgit/dircache/DirCacheBuilder.java |  173 +++++++++++++++++
 3 files changed, 390 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/BaseDirCacheEditor.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/BaseDirCacheEditor.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/BaseDirCacheEditor.java
new file mode 100644
index 0000000..268c01e
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/BaseDirCacheEditor.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.io.IOException;
+
+/**
+ * Generic update/editing support for {@link DirCache}.
+ * <p>
+ * The different update strategies extend this class to provide their own unique
+ * services to applications.
+ */
+abstract class BaseDirCacheEditor {
+	/** The cache instance this editor updates during {@link #finish()}. */
+	protected DirCache cache;
+
+	/**
+	 * Entry table this builder will eventually replace into {@link #cache}.
+	 * <p>
+	 * Use {@link #fastAdd(DirCacheEntry)} or {@link #fastKeep(int, int)} to
+	 * make additions to this table. The table is automatically expanded if it
+	 * is too small for a new addition.
+	 * <p>
+	 * Typically the entries in here are sorted by their path names, just like
+	 * they are in the DirCache instance.
+	 */
+	protected DirCacheEntry[] entries;
+
+	/** Total number of valid entries in {@link #entries}. */
+	protected int entryCnt;
+
+	/**
+	 * Construct a new editor.
+	 * 
+	 * @param dc
+	 *            the cache this editor will eventually update.
+	 * @param ecnt
+	 *            estimated number of entries the editor will have upon
+	 *            completion. This sizes the initial entry table.
+	 */
+	protected BaseDirCacheEditor(final DirCache dc, final int ecnt) {
+		cache = dc;
+		entries = new DirCacheEntry[ecnt];
+	}
+
+	/**
+	 * @return the cache we will update on {@link #finish()}.
+	 */
+	public DirCache getDirCache() {
+		return cache;
+	}
+
+	/**
+	 * Append one entry into the resulting entry list.
+	 * <p>
+	 * The entry is placed at the end of the entry list. The caller is
+	 * responsible for making sure the final table is correctly sorted.
+	 * <p>
+	 * The {@link #entries} table is automatically expanded if there is
+	 * insufficient space for the new addition.
+	 * 
+	 * @param newEntry
+	 *            the new entry to add.
+	 */
+	protected void fastAdd(final DirCacheEntry newEntry) {
+		if (entries.length == entryCnt) {
+			final DirCacheEntry[] n = new DirCacheEntry[(entryCnt + 16) * 3 / 2];
+			System.arraycopy(entries, 0, n, 0, entryCnt);
+			entries = n;
+		}
+		entries[entryCnt++] = newEntry;
+	}
+
+	/**
+	 * Add a range of existing entries from the destination cache.
+	 * <p>
+	 * The entries are placed at the end of the entry list, preserving their
+	 * current order. The caller is responsible for making sure the final table
+	 * is correctly sorted.
+	 * <p>
+	 * This method copies from the destination cache, which has not yet been
+	 * updated with this editor's new table. So all offsets into the destination
+	 * cache are not affected by any updates that may be currently taking place
+	 * in this editor.
+	 * <p>
+	 * The {@link #entries} table is automatically expanded if there is
+	 * insufficient space for the new additions.
+	 * 
+	 * @param pos
+	 *            first entry to copy from the destination cache.
+	 * @param cnt
+	 *            number of entries to copy.
+	 */
+	protected void fastKeep(final int pos, int cnt) {
+		if (entryCnt + cnt > entries.length) {
+			final int m1 = (entryCnt + 16) * 3 / 2;
+			final int m2 = entryCnt + cnt;
+			final DirCacheEntry[] n = new DirCacheEntry[Math.max(m1, m2)];
+			System.arraycopy(entries, 0, n, 0, entryCnt);
+			entries = n;
+		}
+
+		cache.toArray(pos, entries, entryCnt, cnt);
+		entryCnt += cnt;
+	}
+
+	/**
+	 * Finish this builder and update the destination {@link DirCache}.
+	 * <p>
+	 * When this method completes this builder instance is no longer usable by
+	 * the calling application. A new builder must be created to make additional
+	 * changes to the index entries.
+	 * <p>
+	 * After completion the DirCache returned by {@link #getDirCache()} will
+	 * contain all modifications.
+	 * <p>
+	 * <i>Note to implementors:</i> Make sure {@link #entries} is fully sorted
+	 * then invoke {@link #replace()} to update the DirCache with the new table.
+	 */
+	public abstract void finish();
+
+	/**
+	 * Update the DirCache with the contents of {@link #entries}.
+	 * <p>
+	 * This method should be invoked only during an implementation of
+	 * {@link #finish()}, and only after {@link #entries} is sorted.
+	 */
+	protected void replace() {
+		if (entryCnt < entries.length / 2) {
+			final DirCacheEntry[] n = new DirCacheEntry[entryCnt];
+			System.arraycopy(entries, 0, n, 0, entryCnt);
+			entries = n;
+		}
+		cache.replace(entries, entryCnt);
+	}
+
+	/**
+	 * Finish, write, commit this change, and release the index lock.
+	 * <p>
+	 * If this method fails (returns false) the lock is still released.
+	 * <p>
+	 * This is a utility method for applications as the finish-write-commit
+	 * pattern is very common after using a builder to update entries.
+	 * 
+	 * @return true if the commit was successful and the file contains the new
+	 *         data; false if the commit failed and the file remains with the
+	 *         old data.
+	 * @throws IllegalStateException
+	 *             the lock is not held.
+	 * @throws IOException
+	 *             the output file could not be created. The caller no longer
+	 *             holds the lock.
+	 */
+	public boolean commit() throws IOException {
+		finish();
+		cache.write();
+		return cache.commit();
+	}
+}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
index b5fdd46..810b479 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
@@ -236,6 +236,24 @@ public class DirCache {
 	}
 
 	/**
+	 * Create a new builder to update this cache.
+	 * <p>
+	 * Callers should add all entries to the builder, then use
+	 * {@link DirCacheBuilder#finish()} to update this instance.
+	 * 
+	 * @return a new builder instance for this cache.
+	 */
+	public DirCacheBuilder builder() {
+		return new DirCacheBuilder(this, entryCnt + 16);
+	}
+
+	void replace(final DirCacheEntry[] e, final int cnt) {
+		sortedEntries = e;
+		entryCnt = cnt;
+		tree = null;
+	}
+
+	/**
 	 * Read the index from disk, if it has changed on disk.
 	 * <p>
 	 * This method tries to avoid loading the index if it has not changed since
@@ -591,6 +609,11 @@ public class DirCache {
 		return i < 0 ? null : sortedEntries[i];
 	}
 
+	void toArray(final int i, final DirCacheEntry[] dst, final int off,
+			final int cnt) {
+		System.arraycopy(sortedEntries, i, dst, off, cnt);
+	}
+
 	/**
 	 * Obtain (or build) the current cache tree structure.
 	 * <p>
diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java
new file mode 100644
index 0000000..e303b43
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.util.Arrays;
+
+/**
+ * Updates a {@link DirCache} by adding individual {@link DirCacheEntry}s.
+ * <p>
+ * A builder always starts from a clean slate and appends in every single
+ * <code>DirCacheEntry</code> which the final updated index must have to reflect
+ * its new content.
+ * <p>
+ * For maximum performance applications should add entries in path name order.
+ * Adding entries out of order is permitted, however a final sorting pass will
+ * be implicitly performed during {@link #finish()} to correct any out-of-order
+ * entries. Duplicate detection is also delayed until the sorting is complete.
+ */
+public class DirCacheBuilder extends BaseDirCacheEditor {
+	private boolean sorted;
+
+	/**
+	 * Construct a new builder.
+	 * 
+	 * @param dc
+	 *            the cache this builder will eventually update.
+	 * @param ecnt
+	 *            estimated number of entries the builder will have upon
+	 *            completion. This sizes the initial entry table.
+	 */
+	protected DirCacheBuilder(final DirCache dc, final int ecnt) {
+		super(dc, ecnt);
+	}
+
+	/**
+	 * Append one entry into the resulting entry list.
+	 * <p>
+	 * The entry is placed at the end of the entry list. If the entry causes the
+	 * list to now be incorrectly sorted a final sorting phase will be
+	 * automatically enabled within {@link #finish()}.
+	 * <p>
+	 * The internal entry table is automatically expanded if there is
+	 * insufficient space for the new addition.
+	 * 
+	 * @param newEntry
+	 *            the new entry to add.
+	 */
+	public void add(final DirCacheEntry newEntry) {
+		beforeAdd(newEntry);
+		fastAdd(newEntry);
+	}
+
+	/**
+	 * Add a range of existing entries from the destination cache.
+	 * <p>
+	 * The entries are placed at the end of the entry list. If any of the
+	 * entries causes the list to now be incorrectly sorted a final sorting
+	 * phase will be automatically enabled within {@link #finish()}.
+	 * <p>
+	 * This method copies from the destination cache, which has not yet been
+	 * updated with this editor's new table. So all offsets into the destination
+	 * cache are not affected by any updates that may be currently taking place
+	 * in this editor.
+	 * <p>
+	 * The internal entry table is automatically expanded if there is
+	 * insufficient space for the new additions.
+	 * 
+	 * @param pos
+	 *            first entry to copy from the destination cache.
+	 * @param cnt
+	 *            number of entries to copy.
+	 */
+	public void keep(final int pos, int cnt) {
+		beforeAdd(cache.getEntry(pos));
+		fastKeep(pos, cnt);
+	}
+
+	public void finish() {
+		if (!sorted)
+			resort();
+		replace();
+	}
+
+	private void beforeAdd(final DirCacheEntry newEntry) {
+		if (sorted && entryCnt > 0) {
+			final DirCacheEntry lastEntry = entries[entryCnt - 1];
+			final int cr = DirCache.cmp(lastEntry, newEntry);
+			if (cr > 0) {
+				// The new entry sorts before the old entry; we are
+				// no longer sorted correctly. We'll need to redo
+				// the sorting before we can close out the build.
+				//
+				sorted = false;
+			} else if (cr == 0) {
+				// Same file path; we can only insert this if the
+				// stages won't be violated.
+				//
+				final int peStage = lastEntry.getStage();
+				final int dceStage = newEntry.getStage();
+				if (peStage == dceStage)
+					throw bad(newEntry, "Duplicate stages not allowed");
+				if (peStage == 0 || dceStage == 0)
+					throw bad(newEntry, "Mixed stages not allowed");
+				if (peStage > dceStage)
+					sorted = false;
+			}
+		}
+	}
+
+	private void resort() {
+		Arrays.sort(entries, 0, entryCnt, DirCache.ENT_CMP);
+
+		for (int entryIdx = 1; entryIdx < entryCnt; entryIdx++) {
+			final DirCacheEntry pe = entries[entryIdx - 1];
+			final DirCacheEntry ce = entries[entryIdx];
+			final int cr = DirCache.cmp(pe, ce);
+			if (cr == 0) {
+				// Same file path; we can only allow this if the stages
+				// are 1-3 and no 0 exists.
+				//
+				final int peStage = pe.getStage();
+				final int ceStage = ce.getStage();
+				if (peStage == ceStage)
+					throw bad(ce, "Duplicate stages not allowed");
+				if (peStage == 0 || ceStage == 0)
+					throw bad(ce, "Mixed stages not allowed");
+			}
+		}
+
+		sorted = true;
+	}
+
+	private static IllegalStateException bad(final DirCacheEntry a,
+			final String msg) {
+		return new IllegalStateException(msg + ": " + a.getStage() + " "
+				+ a.getPathString());
+	}
+}
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 13/26] Support iterating and building a DirCache at the same time
  2008-08-12  1:07                       ` [EGIT PATCH 12/26] Support recreating a .git/index through DirCache Shawn O. Pearce
@ 2008-08-12  1:08                         ` Shawn O. Pearce
  2008-08-12  1:08                           ` [EGIT PATCH 14/26] Support creating a new DirCacheEntry for an arbitrary path Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

This iterator combines the DirCacheBuilder add-in-order update strategy
with a suitable TreeWalk iterator, allowing entries which the TreeWalk
does not return to the application to be automatically carried into the
new index, and the application to handle the entires which the TreeWalk
does return to it.

This can be used to implement a simple "rm" function which removes one
or more paths from the index, while leaving the rest of the index alone.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../jgit/dircache/DirCacheBuildIterator.java       |  124 ++++++++++++++++++++
 1 files changed, 124 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuildIterator.java

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuildIterator.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuildIterator.java
new file mode 100644
index 0000000..ca934ec
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuildIterator.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.io.IOException;
+
+import org.spearce.jgit.errors.CorruptObjectException;
+import org.spearce.jgit.errors.IncorrectObjectTypeException;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.treewalk.AbstractTreeIterator;
+
+/**
+ * Iterate and update a {@link DirCache} as part of a <code>TreeWalk</code>.
+ * <p>
+ * Like {@link DirCacheIterator} this iterator allows a DirCache to be used in
+ * parallel with other sorts of iterators in a TreeWalk. However any entry which
+ * appears in the source DirCache and which is skipped by the TreeFilter is
+ * automatically copied into {@link DirCacheBuilder}, thus retaining it in the
+ * newly updated index.
+ * <p>
+ * This iterator is suitable for update processes, or even a simple delete
+ * algorithm. For example deleting a path:
+ * 
+ * <pre>
+ * final DirCache dirc = DirCache.lock(db);
+ * final DirCacheBuilder edit = dirc.builder();
+ * 
+ * final TreeWalk walk = new TreeWalk(db);
+ * walk.reset();
+ * walk.setRecursive(true);
+ * walk.setFilter(PathFilter.create(&quot;name/to/remove&quot;));
+ * walk.addTree(new DirCacheBuildIterator(edit));
+ * 
+ * while (walk.next())
+ * 	; // do nothing on a match as we want to remove matches
+ * edit.commit();
+ * </pre>
+ */
+public class DirCacheBuildIterator extends DirCacheIterator {
+	private final DirCacheBuilder builder;
+
+	/**
+	 * Create a new iterator for an already loaded DirCache instance.
+	 * <p>
+	 * The iterator implementation may copy part of the cache's data during
+	 * construction, so the cache must be read in prior to creating the
+	 * iterator.
+	 * 
+	 * @param dcb
+	 *            the cache builder for the cache to walk. The cache must be
+	 *            already loaded into memory.
+	 */
+	public DirCacheBuildIterator(final DirCacheBuilder dcb) {
+		super(dcb.getDirCache());
+		builder = dcb;
+	}
+
+	protected DirCacheBuildIterator(final DirCacheBuildIterator p,
+			final DirCacheTree dct) {
+		super(p, dct);
+		builder = p.builder;
+	}
+
+	@Override
+	public AbstractTreeIterator createSubtreeIterator(final Repository repo)
+			throws IncorrectObjectTypeException, IOException {
+		if (currentSubtree == null)
+			throw new IncorrectObjectTypeException(getEntryObjectId(),
+					Constants.TYPE_TREE);
+		return new DirCacheBuildIterator(this, currentSubtree);
+	}
+
+	@Override
+	public void skip() throws CorruptObjectException {
+		if (currentSubtree != null)
+			builder.keep(cachePos, currentSubtree.getEntrySpan());
+		else
+			builder.add(currentEntry);
+		next();
+	}
+
+	@Override
+	public void stopWalk() {
+		final int cnt = cache.getEntryCount();
+		if (cachePos < cnt)
+			builder.keep(cachePos, cnt - cachePos);
+	}
+}
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 14/26] Support creating a new DirCacheEntry for an arbitrary path
  2008-08-12  1:08                         ` [EGIT PATCH 13/26] Support iterating and building a DirCache at the same time Shawn O. Pearce
@ 2008-08-12  1:08                           ` Shawn O. Pearce
  2008-08-12  1:08                             ` [EGIT PATCH 15/26] Support a simplified model of editing index entries Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

Applications which use a DirCacheBuilder may need to build their own
DirCacheEntry in order to insert new records into the index file.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../org/spearce/jgit/dircache/DirCacheEntry.java   |   24 ++++++++++++++++++++
 1 files changed, 24 insertions(+), 0 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
index 8ca8f22..eba2678 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
@@ -114,6 +114,30 @@ public class DirCacheEntry {
 			in.skip(expLen - actLen);
 	}
 
+	/**
+	 * Create an empty entry.
+	 * 
+	 * @param newPath
+	 *            name of the cache entry.
+	 */
+	public DirCacheEntry(final String newPath) {
+		this(Constants.encode(newPath));
+	}
+
+	/**
+	 * Create an empty entry.
+	 * 
+	 * @param newPath
+	 *            name of the cache entry, in the standard encoding.
+	 */
+	public DirCacheEntry(final byte[] newPath) {
+		info = new byte[INFO_LEN];
+		infoOffset = 0;
+
+		path = newPath;
+		NB.encodeInt16(info, infoOffset + P_FLAGS, path.length);
+	}
+
 	void write(final OutputStream os) throws IOException {
 		final int pathLen = path.length;
 		os.write(info, infoOffset, INFO_LEN);
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 15/26] Support a simplified model of editing index entries
  2008-08-12  1:08                           ` [EGIT PATCH 14/26] Support creating a new DirCacheEntry for an arbitrary path Shawn O. Pearce
@ 2008-08-12  1:08                             ` Shawn O. Pearce
  2008-08-12  1:08                               ` [EGIT PATCH 16/26] Support recursively getting all entries under a subtree path Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

Some applications may find using DirCacheBuilder difficult and/or just
impossible, as it requires inserting entries in order to have any sort
of good performance.

DirCacheEditor tries to bridge that gap by allowing applications to
insert edit commands out-of-order, then merge-sorts those edits onto
the index entry list from the DirCache.  Since the edit list is likely
to be shorter than the index entry list sorting the edit list and doing
a merge-sort onto the index entry list allows for large range copies in
the regions that are not modified.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../src/org/spearce/jgit/dircache/DirCache.java    |   22 ++
 .../org/spearce/jgit/dircache/DirCacheBuilder.java |    2 +
 .../org/spearce/jgit/dircache/DirCacheEditor.java  |  265 ++++++++++++++++++++
 3 files changed, 289 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEditor.java

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
index 810b479..e48fdec 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
@@ -247,6 +247,18 @@ public class DirCache {
 		return new DirCacheBuilder(this, entryCnt + 16);
 	}
 
+	/**
+	 * Create a new editor to recreate this cache.
+	 * <p>
+	 * Callers should add commands to the editor, then use
+	 * {@link DirCacheEditor#finish()} to update this instance.
+	 * 
+	 * @return a new builder instance for this cache.
+	 */
+	public DirCacheEditor editor() {
+		return new DirCacheEditor(this, entryCnt + 16);
+	}
+
 	void replace(final DirCacheEntry[] e, final int cnt) {
 		sortedEntries = e;
 		entryCnt = cnt;
@@ -569,6 +581,16 @@ public class DirCache {
 		return nextIdx;
 	}
 
+	int nextEntry(final byte[] p, final int pLen, int nextIdx) {
+		while (nextIdx < entryCnt) {
+			final DirCacheEntry next = sortedEntries[nextIdx];
+			if (!DirCacheTree.peq(p, next.path, pLen))
+				break;
+			nextIdx++;
+		}
+		return nextIdx;
+	}
+
 	/**
 	 * Total number of file entries stored in the index.
 	 * <p>
diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java
index e303b43..b1d0a1d 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheBuilder.java
@@ -50,6 +50,8 @@ import java.util.Arrays;
  * Adding entries out of order is permitted, however a final sorting pass will
  * be implicitly performed during {@link #finish()} to correct any out-of-order
  * entries. Duplicate detection is also delayed until the sorting is complete.
+ * 
+ * @see DirCacheEditor
  */
 public class DirCacheBuilder extends BaseDirCacheEditor {
 	private boolean sorted;
diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEditor.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEditor.java
new file mode 100644
index 0000000..e371d9e
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEditor.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.spearce.jgit.lib.Constants;
+
+/**
+ * Updates a {@link DirCache} by supplying discrete edit commands.
+ * <p>
+ * An editor updates a DirCache by taking a list of {@link PathEdit} commands
+ * and executing them against the entries of the destination cache to produce a
+ * new cache. This edit style allows applications to insert a few commands and
+ * then have the editor compute the proper entry indexes necessary to perform an
+ * efficient in-order update of the index records. This can be easier to use
+ * than {@link DirCacheBuilder}.
+ * <p>
+ * 
+ * @see DirCacheBuilder
+ */
+public class DirCacheEditor extends BaseDirCacheEditor {
+	private static final Comparator<PathEdit> EDIT_CMP = new Comparator<PathEdit>() {
+		public int compare(final PathEdit o1, final PathEdit o2) {
+			final byte[] a = o1.path;
+			final byte[] b = o2.path;
+			return DirCache.cmp(a, a.length, b, b.length);
+		}
+	};
+
+	private final List<PathEdit> edits;
+
+	/**
+	 * Construct a new editor.
+	 * 
+	 * @param dc
+	 *            the cache this editor will eventually update.
+	 * @param ecnt
+	 *            estimated number of entries the editor will have upon
+	 *            completion. This sizes the initial entry table.
+	 */
+	protected DirCacheEditor(final DirCache dc, final int ecnt) {
+		super(dc, ecnt);
+		edits = new ArrayList<PathEdit>();
+	}
+
+	/**
+	 * Append one edit command to the list of commands to be applied.
+	 * <p>
+	 * Edit commands may be added in any order chosen by the application. They
+	 * are automatically rearranged by the builder to provide the most efficient
+	 * update possible.
+	 * 
+	 * @param edit
+	 *            another edit command.
+	 */
+	public void add(final PathEdit edit) {
+		edits.add(edit);
+	}
+
+	@Override
+	public boolean commit() throws IOException {
+		if (edits.isEmpty()) {
+			// No changes? Don't rewrite the index.
+			//
+			cache.unlock();
+			return true;
+		}
+		return super.commit();
+	}
+
+	public void finish() {
+		if (!edits.isEmpty()) {
+			applyEdits();
+			replace();
+		}
+	}
+
+	private void applyEdits() {
+		Collections.sort(edits, EDIT_CMP);
+
+		final int maxIdx = cache.getEntryCount();
+		int lastIdx = 0;
+		for (final PathEdit e : edits) {
+			int eIdx = cache.findEntry(e.path, e.path.length);
+			final boolean missing = eIdx < 0;
+			if (eIdx < 0)
+				eIdx = -(eIdx + 1);
+			final int cnt = Math.min(eIdx, maxIdx) - lastIdx;
+			if (cnt > 0)
+				fastKeep(lastIdx, cnt);
+			lastIdx = missing ? eIdx : cache.nextEntry(eIdx);
+
+			if (e instanceof DeletePath)
+				continue;
+			if (e instanceof DeleteTree) {
+				lastIdx = cache.nextEntry(e.path, e.path.length, eIdx);
+				continue;
+			}
+
+			final DirCacheEntry ent;
+			if (missing)
+				ent = new DirCacheEntry(e.path);
+			else
+				ent = cache.getEntry(eIdx);
+			e.apply(ent);
+			fastAdd(ent);
+		}
+
+		final int cnt = maxIdx - lastIdx;
+		if (cnt > 0)
+			fastKeep(lastIdx, cnt);
+	}
+
+	/**
+	 * Any index record update.
+	 * <p>
+	 * Applications should subclass and provide their own implementation for the
+	 * {@link #apply(DirCacheEntry)} method. The editor will invoke apply once
+	 * for each record in the index which matches the path name. If there are
+	 * multiple records (for example in stages 1, 2 and 3), the edit instance
+	 * will be called multiple times, once for each stage.
+	 */
+	public abstract static class PathEdit {
+		final byte[] path;
+
+		/**
+		 * Create a new update command by path name.
+		 * 
+		 * @param entryPath
+		 *            path of the file within the repository.
+		 */
+		public PathEdit(final String entryPath) {
+			path = Constants.encode(entryPath);
+		}
+
+		/**
+		 * Create a new update command for an existing entry instance.
+		 * 
+		 * @param ent
+		 *            entry instance to match path of. Only the path of this
+		 *            entry is actually considered during command evaluation.
+		 */
+		public PathEdit(final DirCacheEntry ent) {
+			path = ent.path;
+		}
+
+		/**
+		 * Apply the update to a single cache entry matching the path.
+		 * <p>
+		 * After apply is invoked the entry is added to the output table, and
+		 * will be included in the new index.
+		 * 
+		 * @param ent
+		 *            the entry being processed. All fields are zeroed out if
+		 *            the path is a new path in the index.
+		 */
+		public abstract void apply(DirCacheEntry ent);
+	}
+
+	/**
+	 * Deletes a single file entry from the index.
+	 * <p>
+	 * This deletion command removes only a single file at the given location,
+	 * but removes multiple stages (if present) for that path. To remove a
+	 * complete subtree use {@link DeleteTree} instead.
+	 * 
+	 * @see DeleteTree
+	 */
+	public static final class DeletePath extends PathEdit {
+		/**
+		 * Create a new deletion command by path name.
+		 * 
+		 * @param entryPath
+		 *            path of the file within the repository.
+		 */
+		public DeletePath(final String entryPath) {
+			super(entryPath);
+		}
+
+		/**
+		 * Create a new deletion command for an existing entry instance.
+		 * 
+		 * @param ent
+		 *            entry instance to remove. Only the path of this entry is
+		 *            actually considered during command evaluation.
+		 */
+		public DeletePath(final DirCacheEntry ent) {
+			super(ent);
+		}
+
+		public void apply(final DirCacheEntry ent) {
+			throw new UnsupportedOperationException("No apply in delete");
+		}
+	}
+
+	/**
+	 * Recursively deletes all paths under a subtree.
+	 * <p>
+	 * This deletion command is more generic than {@link DeletePath} as it can
+	 * remove all records which appear recursively under the same subtree.
+	 * Multiple stages are removed (if present) for any deleted entry.
+	 * <p>
+	 * This command will not remove a single file entry. To remove a single file
+	 * use {@link DeletePath}.
+	 * 
+	 * @see DeletePath
+	 */
+	public static final class DeleteTree extends PathEdit {
+		/**
+		 * Create a new tree deletion command by path name.
+		 * 
+		 * @param entryPath
+		 *            path of the subtree within the repository. If the path
+		 *            does not end with "/" a "/" is implicitly added to ensure
+		 *            only the subtree's contents are matched by the command.
+		 */
+		public DeleteTree(final String entryPath) {
+			super(entryPath.endsWith("/") ? entryPath : entryPath + "/");
+		}
+
+		public void apply(final DirCacheEntry ent) {
+			throw new UnsupportedOperationException("No apply in delete");
+		}
+	}
+}
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 16/26] Support recursively getting all entries under a subtree path
  2008-08-12  1:08                             ` [EGIT PATCH 15/26] Support a simplified model of editing index entries Shawn O. Pearce
@ 2008-08-12  1:08                               ` Shawn O. Pearce
  2008-08-12  1:08                                 ` [EGIT PATCH 17/26] Support copying meta fields from one DirCacheEntry to another Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

Some applications need to process through all entries of a subtree.
A good example is if the entries need to be recreated into another
subtree to support a "cp -r a b" or "mv a b" operation.

The getEntriesWithin method returns an array of all entries within
a single subtree path, allowing the aplication to process over that
array in order.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../src/org/spearce/jgit/dircache/DirCache.java    |   22 ++++++++++++++++++++
 1 files changed, 22 insertions(+), 0 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
index e48fdec..302c514 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCache.java
@@ -631,6 +631,28 @@ public class DirCache {
 		return i < 0 ? null : sortedEntries[i];
 	}
 
+	/**
+	 * Recursively get all entries within a subtree.
+	 * 
+	 * @param path
+	 *            the subtree path to get all entries within.
+	 * @return all entries recursively contained within the subtree.
+	 */
+	public DirCacheEntry[] getEntriesWithin(String path) {
+		if (!path.endsWith("/"))
+			path += "/";
+		final byte[] p = Constants.encode(path);
+		final int pLen = p.length;
+
+		int eIdx = findEntry(p, pLen);
+		if (eIdx < 0)
+			eIdx = -(eIdx + 1);
+		final int lastIdx = nextEntry(p, pLen, eIdx);
+		final DirCacheEntry[] r = new DirCacheEntry[lastIdx - eIdx];
+		System.arraycopy(sortedEntries, eIdx, r, 0, r.length);
+		return r;
+	}
+
 	void toArray(final int i, final DirCacheEntry[] dst, final int off,
 			final int cnt) {
 		System.arraycopy(sortedEntries, i, dst, off, cnt);
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 17/26] Support copying meta fields from one DirCacheEntry to another
  2008-08-12  1:08                               ` [EGIT PATCH 16/26] Support recursively getting all entries under a subtree path Shawn O. Pearce
@ 2008-08-12  1:08                                 ` Shawn O. Pearce
  2008-08-12  1:08                                   ` [EGIT PATCH 18/26] Add JUnit tests for new DirCache API Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

When an application moves a DirCacheEntry record from one path to
another path in the index we need to recreate the entry object and
also copy the meta fields from the old entry to the new entry.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../org/spearce/jgit/dircache/DirCacheEntry.java   |   16 ++++++++++++++++
 1 files changed, 16 insertions(+), 0 deletions(-)

diff --git a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
index eba2678..2b5ce03 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/dircache/DirCacheEntry.java
@@ -354,6 +354,22 @@ public class DirCacheEntry {
 		return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
 	}
 
+	/**
+	 * Copy the ObjectId and other meta fields from an existing entry.
+	 * <p>
+	 * This method copies everything except the path from one entry to another,
+	 * supporting renaming.
+	 * 
+	 * @param src
+	 *            the entry to copy ObjectId and meta fields from.
+	 */
+	public void copyMetaData(final DirCacheEntry src) {
+		final int pLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & 0xfff;
+		System.arraycopy(src.info, src.infoOffset, info, infoOffset, INFO_LEN);
+		NB.encodeInt16(info, infoOffset + P_FLAGS, pLen
+				| NB.decodeUInt16(info, infoOffset + P_FLAGS) & ~0xfff);
+	}
+
 	private long decodeTS(final int pIdx) {
 		final int base = infoOffset + pIdx;
 		final int sec = NB.decodeInt32(info, base);
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 18/26] Add JUnit tests for new DirCache API
  2008-08-12  1:08                                 ` [EGIT PATCH 17/26] Support copying meta fields from one DirCacheEntry to another Shawn O. Pearce
@ 2008-08-12  1:08                                   ` Shawn O. Pearce
       [not found]                                     ` <1218503293-14057-20-git-send-email-spearce@spearce.org>
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

These tests excerise the file reading and writing routines, as well as
the two forms of editing the cache (through builder and editor).

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../spearce/jgit/dircache/DirCacheBasicTest.java   |  185 +++++++++++++
 .../jgit/dircache/DirCacheBuilderIteratorTest.java |   91 +++++++
 .../spearce/jgit/dircache/DirCacheBuilderTest.java |  253 ++++++++++++++++++
 .../spearce/jgit/dircache/DirCacheFindTest.java    |   86 ++++++
 .../jgit/dircache/DirCacheIteratorTest.java        |  273 ++++++++++++++++++++
 .../spearce/jgit/dircache/DirCacheTreeTest.java    |  150 +++++++++++
 6 files changed, 1038 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBasicTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBuilderIteratorTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBuilderTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheFindTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheIteratorTest.java
 create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheTreeTest.java

diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBasicTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBasicTest.java
new file mode 100644
index 0000000..b3097ac
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBasicTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.io.File;
+
+import org.spearce.jgit.lib.RepositoryTestCase;
+
+public class DirCacheBasicTest extends RepositoryTestCase {
+	public void testReadMissing_RealIndex() throws Exception {
+		final File idx = new File(db.getDirectory(), "index");
+		assertFalse(idx.exists());
+
+		final DirCache dc = DirCache.read(db);
+		assertNotNull(dc);
+		assertEquals(0, dc.getEntryCount());
+	}
+
+	public void testReadMissing_TempIndex() throws Exception {
+		final File idx = new File(db.getDirectory(), "tmp_index");
+		assertFalse(idx.exists());
+
+		final DirCache dc = DirCache.read(idx);
+		assertNotNull(dc);
+		assertEquals(0, dc.getEntryCount());
+	}
+
+	public void testLockMissing_RealIndex() throws Exception {
+		final File idx = new File(db.getDirectory(), "index");
+		final File lck = new File(db.getDirectory(), "index.lock");
+		assertFalse(idx.exists());
+		assertFalse(lck.exists());
+
+		final DirCache dc = DirCache.lock(db);
+		assertNotNull(dc);
+		assertFalse(idx.exists());
+		assertTrue(lck.exists());
+		assertEquals(0, dc.getEntryCount());
+
+		dc.unlock();
+		assertFalse(idx.exists());
+		assertFalse(lck.exists());
+	}
+
+	public void testLockMissing_TempIndex() throws Exception {
+		final File idx = new File(db.getDirectory(), "tmp_index");
+		final File lck = new File(db.getDirectory(), "tmp_index.lock");
+		assertFalse(idx.exists());
+		assertFalse(lck.exists());
+
+		final DirCache dc = DirCache.lock(idx);
+		assertNotNull(dc);
+		assertFalse(idx.exists());
+		assertTrue(lck.exists());
+		assertEquals(0, dc.getEntryCount());
+
+		dc.unlock();
+		assertFalse(idx.exists());
+		assertFalse(lck.exists());
+	}
+
+	public void testWriteEmptyUnlock_RealIndex() throws Exception {
+		final File idx = new File(db.getDirectory(), "index");
+		final File lck = new File(db.getDirectory(), "index.lock");
+		assertFalse(idx.exists());
+		assertFalse(lck.exists());
+
+		final DirCache dc = DirCache.lock(db);
+		assertEquals(0, lck.length());
+		dc.write();
+		assertEquals(12 + 20, lck.length());
+
+		dc.unlock();
+		assertFalse(idx.exists());
+		assertFalse(lck.exists());
+	}
+
+	public void testWriteEmptyCommit_RealIndex() throws Exception {
+		final File idx = new File(db.getDirectory(), "index");
+		final File lck = new File(db.getDirectory(), "index.lock");
+		assertFalse(idx.exists());
+		assertFalse(lck.exists());
+
+		final DirCache dc = DirCache.lock(db);
+		assertEquals(0, lck.length());
+		dc.write();
+		assertEquals(12 + 20, lck.length());
+
+		assertTrue(dc.commit());
+		assertTrue(idx.exists());
+		assertFalse(lck.exists());
+		assertEquals(12 + 20, idx.length());
+	}
+
+	public void testWriteEmptyReadEmpty_RealIndex() throws Exception {
+		final File idx = new File(db.getDirectory(), "index");
+		final File lck = new File(db.getDirectory(), "index.lock");
+		assertFalse(idx.exists());
+		assertFalse(lck.exists());
+		{
+			final DirCache dc = DirCache.lock(db);
+			dc.write();
+			assertTrue(dc.commit());
+			assertTrue(idx.exists());
+		}
+		{
+			final DirCache dc = DirCache.read(db);
+			assertEquals(0, dc.getEntryCount());
+		}
+	}
+
+	public void testWriteEmptyLockEmpty_RealIndex() throws Exception {
+		final File idx = new File(db.getDirectory(), "index");
+		final File lck = new File(db.getDirectory(), "index.lock");
+		assertFalse(idx.exists());
+		assertFalse(lck.exists());
+		{
+			final DirCache dc = DirCache.lock(db);
+			dc.write();
+			assertTrue(dc.commit());
+			assertTrue(idx.exists());
+		}
+		{
+			final DirCache dc = DirCache.lock(db);
+			assertEquals(0, dc.getEntryCount());
+			assertTrue(idx.exists());
+			assertTrue(lck.exists());
+			dc.unlock();
+		}
+	}
+
+	public void testBuildThenClear() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final String[] paths = { "a.", "a.b", "a/b", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++)
+			ents[i] = new DirCacheEntry(paths[i]);
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = 0; i < ents.length; i++)
+			b.add(ents[i]);
+		b.finish();
+
+		assertEquals(paths.length, dc.getEntryCount());
+		dc.clear();
+		assertEquals(0, dc.getEntryCount());
+	}
+
+}
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBuilderIteratorTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBuilderIteratorTest.java
new file mode 100644
index 0000000..cbcdeb5
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBuilderIteratorTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.util.Collections;
+
+import org.spearce.jgit.lib.FileMode;
+import org.spearce.jgit.lib.RepositoryTestCase;
+import org.spearce.jgit.treewalk.TreeWalk;
+import org.spearce.jgit.treewalk.filter.PathFilterGroup;
+
+public class DirCacheBuilderIteratorTest extends RepositoryTestCase {
+	public void testPathFilterGroup_DoesNotSkipTail() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final FileMode mode = FileMode.REGULAR_FILE;
+		final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++) {
+			ents[i] = new DirCacheEntry(paths[i]);
+			ents[i].setFileMode(mode);
+		}
+		{
+			final DirCacheBuilder b = dc.builder();
+			for (int i = 0; i < ents.length; i++)
+				b.add(ents[i]);
+			b.finish();
+		}
+
+		final int expIdx = 2;
+		final DirCacheBuilder b = dc.builder();
+		final TreeWalk tw = new TreeWalk(db);
+		tw.reset();
+		tw.addTree(new DirCacheBuildIterator(b));
+		tw.setRecursive(true);
+		tw.setFilter(PathFilterGroup.createFromStrings(Collections
+				.singleton(paths[expIdx])));
+
+		assertTrue("found " + paths[expIdx], tw.next());
+		final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
+		assertNotNull(c);
+		assertEquals(expIdx, c.cachePos);
+		assertSame(ents[expIdx], c.getDirCacheEntry());
+		assertEquals(paths[expIdx], tw.getPathString());
+		assertEquals(mode.getBits(), tw.getRawMode(0));
+		assertSame(mode, tw.getFileMode(0));
+		b.add(c.getDirCacheEntry());
+
+		assertFalse("no more entries", tw.next());
+
+		b.finish();
+		assertEquals(ents.length, dc.getEntryCount());
+		for (int i = 0; i < ents.length; i++)
+			assertSame(ents[i], dc.getEntry(i));
+	}
+}
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBuilderTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBuilderTest.java
new file mode 100644
index 0000000..2cf1d92
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheBuilderTest.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.io.File;
+
+import org.spearce.jgit.lib.FileMode;
+import org.spearce.jgit.lib.ObjectId;
+import org.spearce.jgit.lib.RepositoryTestCase;
+
+public class DirCacheBuilderTest extends RepositoryTestCase {
+	public void testBuildEmpty() throws Exception {
+		{
+			final DirCache dc = DirCache.lock(db);
+			final DirCacheBuilder b = dc.builder();
+			assertNotNull(b);
+			b.finish();
+			dc.write();
+			assertTrue(dc.commit());
+		}
+		{
+			final DirCache dc = DirCache.read(db);
+			assertEquals(0, dc.getEntryCount());
+		}
+	}
+
+	public void testBuildOneFile_FinishWriteCommit() throws Exception {
+		final String path = "a-file-path";
+		final FileMode mode = FileMode.REGULAR_FILE;
+		final long lastModified = 1218123387057L;
+		final int length = 1342;
+		final DirCacheEntry entOrig;
+		{
+			final DirCache dc = DirCache.lock(db);
+			final DirCacheBuilder b = dc.builder();
+			assertNotNull(b);
+
+			entOrig = new DirCacheEntry(path);
+			entOrig.setFileMode(mode);
+			entOrig.setLastModified(lastModified);
+			entOrig.setLength(length);
+
+			assertNotSame(path, entOrig.getPathString());
+			assertEquals(path, entOrig.getPathString());
+			assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
+			assertEquals(mode.getBits(), entOrig.getRawMode());
+			assertEquals(0, entOrig.getStage());
+			assertEquals(lastModified, entOrig.getLastModified());
+			assertEquals(length, entOrig.getLength());
+			assertFalse(entOrig.isAssumeValid());
+			b.add(entOrig);
+
+			b.finish();
+			assertEquals(1, dc.getEntryCount());
+			assertSame(entOrig, dc.getEntry(0));
+
+			dc.write();
+			assertTrue(dc.commit());
+		}
+		{
+			final DirCache dc = DirCache.read(db);
+			assertEquals(1, dc.getEntryCount());
+
+			final DirCacheEntry entRead = dc.getEntry(0);
+			assertNotSame(entOrig, entRead);
+			assertEquals(path, entRead.getPathString());
+			assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
+			assertEquals(mode.getBits(), entOrig.getRawMode());
+			assertEquals(0, entOrig.getStage());
+			assertEquals(lastModified, entOrig.getLastModified());
+			assertEquals(length, entOrig.getLength());
+			assertFalse(entOrig.isAssumeValid());
+		}
+	}
+
+	public void testBuildOneFile_Commit() throws Exception {
+		final String path = "a-file-path";
+		final FileMode mode = FileMode.REGULAR_FILE;
+		final long lastModified = 1218123387057L;
+		final int length = 1342;
+		final DirCacheEntry entOrig;
+		{
+			final DirCache dc = DirCache.lock(db);
+			final DirCacheBuilder b = dc.builder();
+			assertNotNull(b);
+
+			entOrig = new DirCacheEntry(path);
+			entOrig.setFileMode(mode);
+			entOrig.setLastModified(lastModified);
+			entOrig.setLength(length);
+
+			assertNotSame(path, entOrig.getPathString());
+			assertEquals(path, entOrig.getPathString());
+			assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
+			assertEquals(mode.getBits(), entOrig.getRawMode());
+			assertEquals(0, entOrig.getStage());
+			assertEquals(lastModified, entOrig.getLastModified());
+			assertEquals(length, entOrig.getLength());
+			assertFalse(entOrig.isAssumeValid());
+			b.add(entOrig);
+
+			assertTrue(b.commit());
+			assertEquals(1, dc.getEntryCount());
+			assertSame(entOrig, dc.getEntry(0));
+			assertFalse(new File(db.getDirectory(), "index.lock").exists());
+		}
+		{
+			final DirCache dc = DirCache.read(db);
+			assertEquals(1, dc.getEntryCount());
+
+			final DirCacheEntry entRead = dc.getEntry(0);
+			assertNotSame(entOrig, entRead);
+			assertEquals(path, entRead.getPathString());
+			assertEquals(ObjectId.zeroId(), entOrig.getObjectId());
+			assertEquals(mode.getBits(), entOrig.getRawMode());
+			assertEquals(0, entOrig.getStage());
+			assertEquals(lastModified, entOrig.getLastModified());
+			assertEquals(length, entOrig.getLength());
+			assertFalse(entOrig.isAssumeValid());
+		}
+	}
+
+	public void testFindSingleFile() throws Exception {
+		final String path = "a-file-path";
+		final DirCache dc = DirCache.read(db);
+		final DirCacheBuilder b = dc.builder();
+		assertNotNull(b);
+
+		final DirCacheEntry entOrig = new DirCacheEntry(path);
+		assertNotSame(path, entOrig.getPathString());
+		assertEquals(path, entOrig.getPathString());
+		b.add(entOrig);
+		b.finish();
+
+		assertEquals(1, dc.getEntryCount());
+		assertSame(entOrig, dc.getEntry(0));
+		assertEquals(0, dc.findEntry(path));
+
+		assertEquals(-1, dc.findEntry("@@-before"));
+		assertEquals(0, real(dc.findEntry("@@-before")));
+
+		assertEquals(-2, dc.findEntry("a-zoo"));
+		assertEquals(1, real(dc.findEntry("a-zoo")));
+
+		assertSame(entOrig, dc.getEntry(path));
+	}
+
+	public void testAdd_InGitSortOrder() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final String[] paths = { "a.", "a.b", "a/b", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++)
+			ents[i] = new DirCacheEntry(paths[i]);
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = 0; i < ents.length; i++)
+			b.add(ents[i]);
+		b.finish();
+
+		assertEquals(paths.length, dc.getEntryCount());
+		for (int i = 0; i < paths.length; i++) {
+			assertSame(ents[i], dc.getEntry(i));
+			assertEquals(paths[i], dc.getEntry(i).getPathString());
+			assertEquals(i, dc.findEntry(paths[i]));
+			assertSame(ents[i], dc.getEntry(paths[i]));
+		}
+	}
+
+	public void testAdd_ReverseGitSortOrder() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final String[] paths = { "a.", "a.b", "a/b", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++)
+			ents[i] = new DirCacheEntry(paths[i]);
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = ents.length - 1; i >= 0; i--)
+			b.add(ents[i]);
+		b.finish();
+
+		assertEquals(paths.length, dc.getEntryCount());
+		for (int i = 0; i < paths.length; i++) {
+			assertSame(ents[i], dc.getEntry(i));
+			assertEquals(paths[i], dc.getEntry(i).getPathString());
+			assertEquals(i, dc.findEntry(paths[i]));
+			assertSame(ents[i], dc.getEntry(paths[i]));
+		}
+	}
+
+	public void testBuilderClear() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final String[] paths = { "a.", "a.b", "a/b", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++)
+			ents[i] = new DirCacheEntry(paths[i]);
+		{
+			final DirCacheBuilder b = dc.builder();
+			for (int i = 0; i < ents.length; i++)
+				b.add(ents[i]);
+			b.finish();
+		}
+		assertEquals(paths.length, dc.getEntryCount());
+		{
+			final DirCacheBuilder b = dc.builder();
+			b.finish();
+		}
+		assertEquals(0, dc.getEntryCount());
+	}
+
+	private static int real(int eIdx) {
+		if (eIdx < 0)
+			eIdx = -(eIdx + 1);
+		return eIdx;
+	}
+}
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheFindTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheFindTest.java
new file mode 100644
index 0000000..0eb0302
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheFindTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import org.spearce.jgit.lib.RepositoryTestCase;
+
+public class DirCacheFindTest extends RepositoryTestCase {
+	public void testEntriesWithin() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++)
+			ents[i] = new DirCacheEntry(paths[i]);
+		final int aFirst = 1;
+		final int aLast = 3;
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = 0; i < ents.length; i++)
+			b.add(ents[i]);
+		b.finish();
+
+		assertEquals(paths.length, dc.getEntryCount());
+		for (int i = 0; i < ents.length; i++)
+			assertSame(ents[i], dc.getEntry(i));
+
+		{
+			final DirCacheEntry[] aContents = dc.getEntriesWithin("a");
+			assertNotNull(aContents);
+			assertEquals(aLast - aFirst + 1, aContents.length);
+			for (int i = aFirst, j = 0; i <= aLast; i++, j++)
+				assertSame(ents[i], aContents[j]);
+		}
+		{
+			final DirCacheEntry[] aContents = dc.getEntriesWithin("a/");
+			assertNotNull(aContents);
+			assertEquals(aLast - aFirst + 1, aContents.length);
+			for (int i = aFirst, j = 0; i <= aLast; i++, j++)
+				assertSame(ents[i], aContents[j]);
+		}
+
+		assertNotNull(dc.getEntriesWithin("a."));
+		assertEquals(0, dc.getEntriesWithin("a.").length);
+
+		assertNotNull(dc.getEntriesWithin("a0b"));
+		assertEquals(0, dc.getEntriesWithin("a0b.").length);
+
+		assertNotNull(dc.getEntriesWithin("zoo"));
+		assertEquals(0, dc.getEntriesWithin("zoo.").length);
+	}
+}
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheIteratorTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheIteratorTest.java
new file mode 100644
index 0000000..7d4e6bb
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheIteratorTest.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import java.util.Collections;
+
+import org.spearce.jgit.lib.FileMode;
+import org.spearce.jgit.lib.RepositoryTestCase;
+import org.spearce.jgit.treewalk.TreeWalk;
+import org.spearce.jgit.treewalk.filter.PathFilterGroup;
+
+public class DirCacheIteratorTest extends RepositoryTestCase {
+	public void testEmptyTree_NoTreeWalk() throws Exception {
+		final DirCache dc = DirCache.read(db);
+		assertEquals(0, dc.getEntryCount());
+
+		final DirCacheIterator i = new DirCacheIterator(dc);
+		i.next();
+		assertTrue(i.eof());
+	}
+
+	public void testEmptyTree_WithTreeWalk() throws Exception {
+		final DirCache dc = DirCache.read(db);
+		assertEquals(0, dc.getEntryCount());
+
+		final TreeWalk tw = new TreeWalk(db);
+		tw.reset();
+		tw.addTree(new DirCacheIterator(dc));
+		assertFalse(tw.next());
+	}
+
+	public void testNoSubtree_NoTreeWalk() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final String[] paths = { "a.", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++)
+			ents[i] = new DirCacheEntry(paths[i]);
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = 0; i < ents.length; i++)
+			b.add(ents[i]);
+		b.finish();
+
+		final DirCacheIterator i = new DirCacheIterator(dc);
+		int pathIdx = 0;
+		for (;;) {
+			i.next();
+			if (i.eof())
+				break;
+			assertEquals(pathIdx, i.cachePos);
+			assertSame(ents[pathIdx], i.getDirCacheEntry());
+			pathIdx++;
+		}
+		assertEquals(pathIdx, paths.length);
+	}
+
+	public void testNoSubtree_WithTreeWalk() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final String[] paths = { "a.", "a0b" };
+		final FileMode[] modes = { FileMode.EXECUTABLE_FILE, FileMode.GITLINK };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++) {
+			ents[i] = new DirCacheEntry(paths[i]);
+			ents[i].setFileMode(modes[i]);
+		}
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = 0; i < ents.length; i++)
+			b.add(ents[i]);
+		b.finish();
+
+		final DirCacheIterator i = new DirCacheIterator(dc);
+		final TreeWalk tw = new TreeWalk(db);
+		tw.reset();
+		tw.addTree(i);
+		int pathIdx = 0;
+		while (tw.next()) {
+			assertSame(i, tw.getTree(0, DirCacheIterator.class));
+			assertEquals(pathIdx, i.cachePos);
+			assertSame(ents[pathIdx], i.getDirCacheEntry());
+			assertEquals(paths[pathIdx], tw.getPathString());
+			assertEquals(modes[pathIdx].getBits(), tw.getRawMode(0));
+			assertSame(modes[pathIdx], tw.getFileMode(0));
+			pathIdx++;
+		}
+		assertEquals(pathIdx, paths.length);
+	}
+
+	public void testSingleSubtree_NoRecursion() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++) {
+			ents[i] = new DirCacheEntry(paths[i]);
+			ents[i].setFileMode(FileMode.REGULAR_FILE);
+		}
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = 0; i < ents.length; i++)
+			b.add(ents[i]);
+		b.finish();
+
+		final String[] expPaths = { "a.", "a", "a0b" };
+		final FileMode[] expModes = { FileMode.REGULAR_FILE, FileMode.TREE,
+				FileMode.REGULAR_FILE };
+		final int expPos[] = { 0, -1, 4 };
+
+		final DirCacheIterator i = new DirCacheIterator(dc);
+		final TreeWalk tw = new TreeWalk(db);
+		tw.reset();
+		tw.addTree(i);
+		tw.setRecursive(false);
+		int pathIdx = 0;
+		while (tw.next()) {
+			assertSame(i, tw.getTree(0, DirCacheIterator.class));
+			assertEquals(expModes[pathIdx].getBits(), tw.getRawMode(0));
+			assertSame(expModes[pathIdx], tw.getFileMode(0));
+			assertEquals(expPaths[pathIdx], tw.getPathString());
+
+			if (expPos[pathIdx] >= 0) {
+				assertEquals(expPos[pathIdx], i.cachePos);
+				assertSame(ents[expPos[pathIdx]], i.getDirCacheEntry());
+			} else {
+				assertSame(FileMode.TREE, tw.getFileMode(0));
+			}
+
+			pathIdx++;
+		}
+		assertEquals(pathIdx, expPaths.length);
+	}
+
+	public void testSingleSubtree_Recursive() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final FileMode mode = FileMode.REGULAR_FILE;
+		final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++) {
+			ents[i] = new DirCacheEntry(paths[i]);
+			ents[i].setFileMode(mode);
+		}
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = 0; i < ents.length; i++)
+			b.add(ents[i]);
+		b.finish();
+
+		final DirCacheIterator i = new DirCacheIterator(dc);
+		final TreeWalk tw = new TreeWalk(db);
+		tw.reset();
+		tw.addTree(i);
+		tw.setRecursive(true);
+		int pathIdx = 0;
+		while (tw.next()) {
+			final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
+			assertNotNull(c);
+			assertEquals(pathIdx, c.cachePos);
+			assertSame(ents[pathIdx], c.getDirCacheEntry());
+			assertEquals(paths[pathIdx], tw.getPathString());
+			assertEquals(mode.getBits(), tw.getRawMode(0));
+			assertSame(mode, tw.getFileMode(0));
+			pathIdx++;
+		}
+		assertEquals(pathIdx, paths.length);
+	}
+
+	public void testTwoLevelSubtree_Recursive() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final FileMode mode = FileMode.REGULAR_FILE;
+		final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++) {
+			ents[i] = new DirCacheEntry(paths[i]);
+			ents[i].setFileMode(mode);
+		}
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = 0; i < ents.length; i++)
+			b.add(ents[i]);
+		b.finish();
+
+		final TreeWalk tw = new TreeWalk(db);
+		tw.reset();
+		tw.addTree(new DirCacheIterator(dc));
+		tw.setRecursive(true);
+		int pathIdx = 0;
+		while (tw.next()) {
+			final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
+			assertNotNull(c);
+			assertEquals(pathIdx, c.cachePos);
+			assertSame(ents[pathIdx], c.getDirCacheEntry());
+			assertEquals(paths[pathIdx], tw.getPathString());
+			assertEquals(mode.getBits(), tw.getRawMode(0));
+			assertSame(mode, tw.getFileMode(0));
+			pathIdx++;
+		}
+		assertEquals(pathIdx, paths.length);
+	}
+
+	public void testTwoLevelSubtree_FilterPath() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final FileMode mode = FileMode.REGULAR_FILE;
+		final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++) {
+			ents[i] = new DirCacheEntry(paths[i]);
+			ents[i].setFileMode(mode);
+		}
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = 0; i < ents.length; i++)
+			b.add(ents[i]);
+		b.finish();
+
+		final TreeWalk tw = new TreeWalk(db);
+		for (int victimIdx = 0; victimIdx < paths.length; victimIdx++) {
+			tw.reset();
+			tw.addTree(new DirCacheIterator(dc));
+			tw.setFilter(PathFilterGroup.createFromStrings(Collections
+					.singleton(paths[victimIdx])));
+			tw.setRecursive(tw.getFilter().shouldBeRecursive());
+			assertTrue(tw.next());
+			final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
+			assertNotNull(c);
+			assertEquals(victimIdx, c.cachePos);
+			assertSame(ents[victimIdx], c.getDirCacheEntry());
+			assertEquals(paths[victimIdx], tw.getPathString());
+			assertEquals(mode.getBits(), tw.getRawMode(0));
+			assertSame(mode, tw.getFileMode(0));
+			assertFalse(tw.next());
+		}
+	}
+}
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheTreeTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheTreeTest.java
new file mode 100644
index 0000000..b37095d
--- /dev/null
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/dircache/DirCacheTreeTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.dircache;
+
+import org.spearce.jgit.lib.RepositoryTestCase;
+
+public class DirCacheTreeTest extends RepositoryTestCase {
+	public void testEmptyCache_NoCacheTree() throws Exception {
+		final DirCache dc = DirCache.read(db);
+		assertNull(dc.getCacheTree(false));
+	}
+
+	public void testEmptyCache_CreateEmptyCacheTree() throws Exception {
+		final DirCache dc = DirCache.read(db);
+		final DirCacheTree tree = dc.getCacheTree(true);
+		assertNotNull(tree);
+		assertSame(tree, dc.getCacheTree(false));
+		assertSame(tree, dc.getCacheTree(true));
+		assertEquals("", tree.getNameString());
+		assertEquals("", tree.getPathString());
+		assertEquals(0, tree.getChildCount());
+		assertEquals(0, tree.getEntrySpan());
+		assertFalse(tree.isValid());
+	}
+
+	public void testEmptyCache_Clear_NoCacheTree() throws Exception {
+		final DirCache dc = DirCache.read(db);
+		final DirCacheTree tree = dc.getCacheTree(true);
+		assertNotNull(tree);
+		dc.clear();
+		assertNull(dc.getCacheTree(false));
+		assertNotSame(tree, dc.getCacheTree(true));
+	}
+
+	public void testSingleSubtree() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++)
+			ents[i] = new DirCacheEntry(paths[i]);
+		final int aFirst = 1;
+		final int aLast = 3;
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = 0; i < ents.length; i++)
+			b.add(ents[i]);
+		b.finish();
+
+		assertNull(dc.getCacheTree(false));
+		final DirCacheTree root = dc.getCacheTree(true);
+		assertNotNull(root);
+		assertSame(root, dc.getCacheTree(true));
+		assertEquals("", root.getNameString());
+		assertEquals("", root.getPathString());
+		assertEquals(1, root.getChildCount());
+		assertEquals(dc.getEntryCount(), root.getEntrySpan());
+		assertFalse(root.isValid());
+
+		final DirCacheTree aTree = root.getChild(0);
+		assertNotNull(aTree);
+		assertSame(aTree, root.getChild(0));
+		assertEquals("a", aTree.getNameString());
+		assertEquals("a/", aTree.getPathString());
+		assertEquals(0, aTree.getChildCount());
+		assertEquals(aLast - aFirst + 1, aTree.getEntrySpan());
+		assertFalse(aTree.isValid());
+	}
+
+	public void testTwoLevelSubtree() throws Exception {
+		final DirCache dc = DirCache.read(db);
+
+		final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" };
+		final DirCacheEntry[] ents = new DirCacheEntry[paths.length];
+		for (int i = 0; i < paths.length; i++)
+			ents[i] = new DirCacheEntry(paths[i]);
+		final int aFirst = 1;
+		final int aLast = 4;
+		final int acFirst = 2;
+		final int acLast = 3;
+
+		final DirCacheBuilder b = dc.builder();
+		for (int i = 0; i < ents.length; i++)
+			b.add(ents[i]);
+		b.finish();
+
+		assertNull(dc.getCacheTree(false));
+		final DirCacheTree root = dc.getCacheTree(true);
+		assertNotNull(root);
+		assertSame(root, dc.getCacheTree(true));
+		assertEquals("", root.getNameString());
+		assertEquals("", root.getPathString());
+		assertEquals(1, root.getChildCount());
+		assertEquals(dc.getEntryCount(), root.getEntrySpan());
+		assertFalse(root.isValid());
+
+		final DirCacheTree aTree = root.getChild(0);
+		assertNotNull(aTree);
+		assertSame(aTree, root.getChild(0));
+		assertEquals("a", aTree.getNameString());
+		assertEquals("a/", aTree.getPathString());
+		assertEquals(1, aTree.getChildCount());
+		assertEquals(aLast - aFirst + 1, aTree.getEntrySpan());
+		assertFalse(aTree.isValid());
+
+		final DirCacheTree acTree = aTree.getChild(0);
+		assertNotNull(acTree);
+		assertSame(acTree, aTree.getChild(0));
+		assertEquals("c", acTree.getNameString());
+		assertEquals("a/c/", acTree.getPathString());
+		assertEquals(0, acTree.getChildCount());
+		assertEquals(acLast - acFirst + 1, acTree.getEntrySpan());
+		assertFalse(acTree.isValid());
+	}
+}
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 20/26] Allow the new DirCacheIterator in command line arguments
       [not found]                                     ` <1218503293-14057-20-git-send-email-spearce@spearce.org>
@ 2008-08-12  1:08                                       ` Shawn O. Pearce
  2008-08-12  1:08                                         ` [EGIT PATCH 21/26] Add debugging commands to interact with the new DirCache code Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

By assuming files passed where we want an AbtractTreeIterator to
be supplied are actually 'DIRC' files (aka .git/index) we can
automatically load them for the command line tool.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../jgit/pgm/opt/AbstractTreeIteratorHandler.java  |   13 +++++++++++++
 1 files changed, 13 insertions(+), 0 deletions(-)

diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/opt/AbstractTreeIteratorHandler.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/opt/AbstractTreeIteratorHandler.java
index 8e216c7..e439c87 100644
--- a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/opt/AbstractTreeIteratorHandler.java
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/opt/AbstractTreeIteratorHandler.java
@@ -46,6 +46,8 @@ import org.kohsuke.args4j.OptionDef;
 import org.kohsuke.args4j.spi.OptionHandler;
 import org.kohsuke.args4j.spi.Parameters;
 import org.kohsuke.args4j.spi.Setter;
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheIterator;
 import org.spearce.jgit.errors.IncorrectObjectTypeException;
 import org.spearce.jgit.errors.MissingObjectException;
 import org.spearce.jgit.lib.ObjectId;
@@ -87,6 +89,17 @@ public class AbstractTreeIteratorHandler extends
 			return 1;
 		}
 
+		if (new File(name).isFile()) {
+			final DirCache dirc;
+			try {
+				dirc = DirCache.read(new File(name));
+			} catch (IOException e) {
+				throw new CmdLineException(name + " is not an index file", e);
+			}
+			setter.addValue(new DirCacheIterator(dirc));
+			return 1;
+		}
+
 		final ObjectId id;
 		try {
 			id = clp.getRepository().resolve(name);
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 21/26] Add debugging commands to interact with the new DirCache code
  2008-08-12  1:08                                       ` [EGIT PATCH 20/26] Allow the new DirCacheIterator in command line arguments Shawn O. Pearce
@ 2008-08-12  1:08                                         ` Shawn O. Pearce
  2008-08-12  1:08                                           ` [EGIT PATCH 22/26] Add a basic command line implementation of rm Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

These debug commands allow the command line user to read or write
the dircache, to exercise the code, and test it against C Git.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../services/org.spearce.jgit.pgm.TextBuiltin      |    5 ++
 .../org/spearce/jgit/pgm/debug/MakeCacheTree.java  |   67 ++++++++++++++++++
 .../org/spearce/jgit/pgm/debug/ReadDirCache.java   |   53 ++++++++++++++
 .../org/spearce/jgit/pgm/debug/ShowCacheTree.java  |   69 +++++++++++++++++++
 .../org/spearce/jgit/pgm/debug/ShowDirCache.java   |   72 ++++++++++++++++++++
 .../org/spearce/jgit/pgm/debug/WriteDirCache.java  |   54 +++++++++++++++
 6 files changed, 320 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/MakeCacheTree.java
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ReadDirCache.java
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ShowCacheTree.java
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ShowDirCache.java
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/WriteDirCache.java

diff --git a/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin b/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin
index 734de3d..8ff815a 100644
--- a/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin
+++ b/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin
@@ -12,4 +12,9 @@ org.spearce.jgit.pgm.RevList
 org.spearce.jgit.pgm.ShowRev
 org.spearce.jgit.pgm.Tag
 
+org.spearce.jgit.pgm.debug.MakeCacheTree
+org.spearce.jgit.pgm.debug.ReadDirCache
+org.spearce.jgit.pgm.debug.ShowCacheTree
 org.spearce.jgit.pgm.debug.ShowCommands
+org.spearce.jgit.pgm.debug.ShowDirCache
+org.spearce.jgit.pgm.debug.WriteDirCache
diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/MakeCacheTree.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/MakeCacheTree.java
new file mode 100644
index 0000000..a7d7438
--- /dev/null
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/MakeCacheTree.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.pgm.debug;
+
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheTree;
+import org.spearce.jgit.pgm.TextBuiltin;
+
+class MakeCacheTree extends TextBuiltin {
+	@Override
+	protected void run() throws Exception {
+		final DirCache cache = DirCache.read(db);
+		final DirCacheTree tree = cache.getCacheTree(true);
+		show(tree);
+	}
+
+	private void show(final DirCacheTree tree) {
+		out.print("\"");
+		out.print(tree.getPathString());
+		out.print("\"");
+		out.print(":  ");
+		out.print(tree.getEntrySpan());
+		out.print(" entries");
+		out.print(", ");
+		out.print(tree.getChildCount());
+		out.print(" children");
+		out.println();
+
+		for (int i = 0; i < tree.getChildCount(); i++)
+			show(tree.getChild(i));
+	}
+}
diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ReadDirCache.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ReadDirCache.java
new file mode 100644
index 0000000..d92a9fd
--- /dev/null
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ReadDirCache.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.pgm.debug;
+
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.pgm.TextBuiltin;
+
+class ReadDirCache extends TextBuiltin {
+	@Override
+	protected void run() throws Exception {
+		final int cnt = 100;
+		final long start = System.currentTimeMillis();
+		for (int i = 0; i < cnt; i++)
+			DirCache.read(db);
+		final long end = System.currentTimeMillis();
+		out.println(" average " + ((end - start) / cnt) + " ms/read");
+	}
+}
diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ShowCacheTree.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ShowCacheTree.java
new file mode 100644
index 0000000..0683fa7
--- /dev/null
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ShowCacheTree.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.pgm.debug;
+
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheTree;
+import org.spearce.jgit.pgm.TextBuiltin;
+
+class ShowCacheTree extends TextBuiltin {
+	@Override
+	protected void run() throws Exception {
+		final DirCache cache = DirCache.read(db);
+		final DirCacheTree tree = cache.getCacheTree(false);
+		if (tree == null)
+			throw die("no 'TREE' section in index");
+		show(tree);
+	}
+
+	private void show(final DirCacheTree tree) {
+		out.print("\"");
+		out.print(tree.getPathString());
+		out.print("\"");
+		out.print(":  ");
+		out.print(tree.getEntrySpan());
+		out.print(" entries");
+		out.print(", ");
+		out.print(tree.getChildCount());
+		out.print(" children");
+		out.println();
+
+		for (int i = 0; i < tree.getChildCount(); i++)
+			show(tree.getChild(i));
+	}
+}
diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ShowDirCache.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ShowDirCache.java
new file mode 100644
index 0000000..0822503
--- /dev/null
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/ShowDirCache.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.pgm.debug;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheEntry;
+import org.spearce.jgit.lib.FileMode;
+import org.spearce.jgit.pgm.TextBuiltin;
+
+class ShowDirCache extends TextBuiltin {
+	@Override
+	protected void run() throws Exception {
+		final SimpleDateFormat fmt;
+		fmt = new SimpleDateFormat("yyyyMMdd,HHmmss.SSS");
+
+		final DirCache cache = DirCache.read(db);
+		for (int i = 0; i < cache.getEntryCount(); i++) {
+			final DirCacheEntry ent = cache.getEntry(i);
+			final FileMode mode = FileMode.fromBits(ent.getRawMode());
+			final int len = ent.getLength();
+			final Date mtime = new Date(ent.getLastModified());
+
+			out.print(mode);
+			out.format(" %6d", len);
+			out.print(' ');
+			out.print(fmt.format(mtime));
+			out.print(' ');
+			out.print(ent.getObjectId());
+			out.print('\t');
+			out.print(ent.getPathString());
+			out.println();
+		}
+	}
+}
diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/WriteDirCache.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/WriteDirCache.java
new file mode 100644
index 0000000..a613cbe
--- /dev/null
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/debug/WriteDirCache.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ *
+ * 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 Git Development Community 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.spearce.jgit.pgm.debug;
+
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.pgm.TextBuiltin;
+
+class WriteDirCache extends TextBuiltin {
+	@Override
+	protected void run() throws Exception {
+		final DirCache cache = DirCache.read(db);
+		if (!cache.lock())
+			throw die("failed to lock index");
+		cache.read();
+		cache.write();
+		if (!cache.commit())
+			throw die("failed to commit index");
+	}
+}
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 22/26] Add a basic command line implementation of rm
  2008-08-12  1:08                                         ` [EGIT PATCH 21/26] Add debugging commands to interact with the new DirCache code Shawn O. Pearce
@ 2008-08-12  1:08                                           ` Shawn O. Pearce
  2008-08-12  1:08                                             ` [EGIT PATCH 23/26] Rewrite GitMoveDeleteHook to use DirCacheBuilder Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

Removing entries from the index and from the working directory is a
very trivial operation now that we have DirCacheBuilder able to do
an update of the index, automatically keeping the paths that were
skipped over by the TreeWalk.  Therefore we only need to match the
paths the user wants to remove, and do nothing with them.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../services/org.spearce.jgit.pgm.TextBuiltin      |    1 +
 .../src/org/spearce/jgit/pgm/Rm.java               |   92 ++++++++++++++++++++
 2 files changed, 93 insertions(+), 0 deletions(-)
 create mode 100644 org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Rm.java

diff --git a/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin b/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin
index 8ff815a..39ae664 100644
--- a/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin
+++ b/org.spearce.jgit.pgm/src/META-INF/services/org.spearce.jgit.pgm.TextBuiltin
@@ -9,6 +9,7 @@ org.spearce.jgit.pgm.LsTree
 org.spearce.jgit.pgm.MergeBase
 org.spearce.jgit.pgm.Push
 org.spearce.jgit.pgm.RevList
+org.spearce.jgit.pgm.Rm
 org.spearce.jgit.pgm.ShowRev
 org.spearce.jgit.pgm.Tag
 
diff --git a/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Rm.java b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Rm.java
new file mode 100644
index 0000000..9a0d863
--- /dev/null
+++ b/org.spearce.jgit.pgm/src/org/spearce/jgit/pgm/Rm.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008, Google Inc.
+ *
+ * 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 Git Development Community 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.spearce.jgit.pgm;
+
+import java.io.File;
+
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.spi.StopOptionHandler;
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheBuildIterator;
+import org.spearce.jgit.dircache.DirCacheBuilder;
+import org.spearce.jgit.lib.Constants;
+import org.spearce.jgit.lib.FileMode;
+import org.spearce.jgit.pgm.opt.PathTreeFilterHandler;
+import org.spearce.jgit.treewalk.TreeWalk;
+import org.spearce.jgit.treewalk.filter.TreeFilter;
+
+@Command(usage = "Stop tracking a file", common = true)
+class Rm extends TextBuiltin {
+	@Argument(metaVar = "path", usage = "path", multiValued = true, required = true, handler = PathTreeFilterHandler.class)
+	@Option(name = "--", handler = StopOptionHandler.class)
+	private TreeFilter paths;
+
+	private File root;
+
+	@Override
+	protected void run() throws Exception {
+		root = db.getWorkDir();
+
+		final DirCache dirc = DirCache.lock(db);
+		final DirCacheBuilder edit = dirc.builder();
+
+		final TreeWalk walk = new TreeWalk(db);
+		walk.reset(); // drop the first empty tree, which we do not need here
+		walk.setRecursive(true);
+		walk.setFilter(paths);
+		walk.addTree(new DirCacheBuildIterator(edit));
+
+		while (walk.next()) {
+			final File path = new File(root, walk.getPathString());
+			final FileMode mode = walk.getFileMode(0);
+			if (mode.getObjectType() == Constants.OBJ_BLOB) {
+				// Deleting a blob is simply a matter of removing
+				// the file or symlink named by the tree entry.
+				delete(path);
+			}
+		}
+
+		edit.commit();
+	}
+
+	private void delete(File p) {
+		while (p != null && !p.equals(root) && p.delete())
+			p = p.getParentFile();
+	}
+}
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 23/26] Rewrite GitMoveDeleteHook to use DirCacheBuilder
  2008-08-12  1:08                                           ` [EGIT PATCH 22/26] Add a basic command line implementation of rm Shawn O. Pearce
@ 2008-08-12  1:08                                             ` Shawn O. Pearce
  2008-08-12  1:08                                               ` [EGIT PATCH 24/26] Teach GitMoveDeleteHook how to move a folder recursively Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

The new DirCache API can read and write an index file faster than
the older GitIndex API can.  Its slightly more verbose to use but
operations will execute more quickly, especially when we are forced
to perform these single path updates by Eclipse.

When we move a file we maintain its stage status.  If the file has
been modified on disk but is not yet staged into the index (or it
was modified since staging) we leave the index alone, retaining
that staged state.

If we are moving within the same index file (same repository) we
only write the index out once.  This is a common case as most of
the renames performed by users will be within the same project,
and the same Git repository.  It is rare for a rename to wind up
spanning multiple repositories.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../org/spearce/egit/core/GitMoveDeleteHook.java   |  105 ++++++++++++--------
 1 files changed, 63 insertions(+), 42 deletions(-)

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/GitMoveDeleteHook.java b/org.spearce.egit.core/src/org/spearce/egit/core/GitMoveDeleteHook.java
index cc4059c..409f018 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/GitMoveDeleteHook.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/GitMoveDeleteHook.java
@@ -8,13 +8,13 @@
  *******************************************************************************/
 package org.spearce.egit.core;
 
-import java.io.File;
 import java.io.IOException;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IFolder;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.team.IMoveDeleteHook;
 import org.eclipse.core.resources.team.IResourceTree;
 import org.eclipse.core.runtime.Assert;
@@ -23,8 +23,10 @@ import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.spearce.egit.core.project.GitProjectData;
 import org.spearce.egit.core.project.RepositoryMapping;
-import org.spearce.jgit.lib.GitIndex;
-import org.spearce.jgit.lib.Repository;
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheBuilder;
+import org.spearce.jgit.dircache.DirCacheEditor;
+import org.spearce.jgit.dircache.DirCacheEntry;
 
 class GitMoveDeleteHook implements IMoveDeleteHook {
 	private static final boolean I_AM_DONE = true;
@@ -40,21 +42,37 @@ class GitMoveDeleteHook implements IMoveDeleteHook {
 
 	public boolean deleteFile(final IResourceTree tree, final IFile file,
 			final int updateFlags, final IProgressMonitor monitor) {
+		final boolean force = (updateFlags & IResource.FORCE) == IResource.FORCE;
+		if (!force && !tree.isSynchronized(file, IResource.DEPTH_ZERO))
+			return false;
+
+		final RepositoryMapping map = RepositoryMapping.getMapping(file);
+		if (map == null)
+			return false;
+
 		try {
-			RepositoryMapping map = RepositoryMapping.getMapping(file);
-			if (map != null) {
-				Repository repository = map.getRepository();
-				GitIndex index = repository.getIndex();
-				if (index.remove(map.getWorkDir(), file.getLocation().toFile()))
-					index.write();
+			final DirCache dirc = DirCache.lock(map.getRepository());
+			final int first = dirc.findEntry(map.getRepoRelativePath(file));
+			if (first < 0) {
+				dirc.unlock();
+				return false;
 			}
-			return FINISH_FOR_ME;
+
+			final DirCacheBuilder edit = dirc.builder();
+			if (first > 0)
+				edit.keep(0, first);
+			final int next = dirc.nextEntry(first);
+			if (next < dirc.getEntryCount())
+				edit.keep(next, dirc.getEntryCount() - next);
+			if (!edit.commit())
+				tree.failed(new Status(IStatus.ERROR, Activator.getPluginId(),
+						0, CoreText.MoveDeleteHook_operationError, null));
+			tree.standardDeleteFile(file, updateFlags, monitor);
 		} catch (IOException e) {
-			e.printStackTrace();
 			tree.failed(new Status(IStatus.ERROR, Activator.getPluginId(), 0,
 					CoreText.MoveDeleteHook_operationError, e));
-			return I_AM_DONE;
 		}
+		return true;
 	}
 
 	public boolean deleteFolder(final IResourceTree tree, final IFolder folder,
@@ -77,45 +95,48 @@ class GitMoveDeleteHook implements IMoveDeleteHook {
 		return FINISH_FOR_ME;
 	}
 
-	public boolean moveFile(final IResourceTree tree, final IFile source,
-			final IFile destination, final int updateFlags,
+	public boolean moveFile(final IResourceTree tree, final IFile srcf,
+			final IFile dstf, final int updateFlags,
 			final IProgressMonitor monitor) {
+		final boolean force = (updateFlags & IResource.FORCE) == IResource.FORCE;
+		if (!force && !tree.isSynchronized(srcf, IResource.DEPTH_ZERO))
+			return false;
+
+		final RepositoryMapping srcm = RepositoryMapping.getMapping(srcf);
+		if (srcm == null)
+			return false;
+		final RepositoryMapping dstm = RepositoryMapping.getMapping(dstf);
+
 		try {
-			final RepositoryMapping map1 = RepositoryMapping.getMapping(source);
-			if (map1 == null) {
-				// Source is not in a Git controlled project, fine
-				return FINISH_FOR_ME;
-			}
-			final GitIndex index1 = map1.getRepository().getIndex();
-			final RepositoryMapping map2 = RepositoryMapping.getMapping(destination);
-			final File sourceFile = source.getLocation().toFile();
-			if (map2 == null) {
-				if (index1.getEntry(Repository.stripWorkDir(map1.getWorkDir(), sourceFile)) == null) {
-					// if the source resource is not tracked by Git that is ok too
-					return FINISH_FOR_ME;
-				}
-				tree.failed(new Status(IStatus.ERROR, Activator.getPluginId(),
-						0, "Destination not in a git versioned project", null));
-				return FINISH_FOR_ME;
+			final DirCache sCache = DirCache.lock(srcm.getRepository());
+			final String sPath = srcm.getRepoRelativePath(srcf);
+			final DirCacheEntry sEnt = sCache.getEntry(sPath);
+			if (sEnt == null) {
+				sCache.unlock();
+				return false;
 			}
-			GitIndex index2 = map2.getRepository().getIndex();
-			tree.standardMoveFile(source, destination, updateFlags, monitor);
-			if (index1.remove(map1.getWorkDir(), sourceFile)) {
-				index2.add(map2.getWorkDir(), destination.getLocation().toFile());
-				index1.write();
-				if (index2 != index1)
-					index2.write();
+
+			final DirCacheEditor sEdit = sCache.editor();
+			sEdit.add(new DirCacheEditor.DeletePath(sEnt));
+			if (dstm != null && dstm.getRepository() == srcm.getRepository()) {
+				final String dPath = srcm.getRepoRelativePath(dstf);
+				sEdit.add(new DirCacheEditor.PathEdit(dPath) {
+					@Override
+					public void apply(final DirCacheEntry dEnt) {
+						dEnt.copyMetaData(sEnt);
+					}
+				});
 			}
-			return I_AM_DONE;
+			if (!sEdit.commit())
+				tree.failed(new Status(IStatus.ERROR, Activator.getPluginId(),
+						0, CoreText.MoveDeleteHook_operationError, null));
 
+			tree.standardMoveFile(srcf, dstf, updateFlags, monitor);
 		} catch (IOException e) {
-			// Recover properly!
-			e.printStackTrace();
 			tree.failed(new Status(IStatus.ERROR, Activator.getPluginId(), 0,
 					CoreText.MoveDeleteHook_operationError, e));
-			return I_AM_DONE;
-
 		}
+		return true;
 	}
 
 	public boolean moveFolder(final IResourceTree tree, final IFolder source,
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 24/26] Teach GitMoveDeleteHook how to move a folder recursively
  2008-08-12  1:08                                             ` [EGIT PATCH 23/26] Rewrite GitMoveDeleteHook to use DirCacheBuilder Shawn O. Pearce
@ 2008-08-12  1:08                                               ` Shawn O. Pearce
  2008-08-12  1:08                                                 ` [EGIT PATCH 25/26] Rewrite UntrackOperation to use DirCacheBuilder Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

When the user renames or moves a folder Eclipse passes us one event
to move the entire container and its contents.  So we need to move
all of the files contained within that container in our index.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../org/spearce/egit/core/GitMoveDeleteHook.java   |   51 ++++++++++++++++++--
 1 files changed, 46 insertions(+), 5 deletions(-)

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/GitMoveDeleteHook.java b/org.spearce.egit.core/src/org/spearce/egit/core/GitMoveDeleteHook.java
index 409f018..2cdff7d 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/GitMoveDeleteHook.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/GitMoveDeleteHook.java
@@ -1,6 +1,7 @@
 /*******************************************************************************
  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Google Inc.
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
@@ -139,12 +140,52 @@ class GitMoveDeleteHook implements IMoveDeleteHook {
 		return true;
 	}
 
-	public boolean moveFolder(final IResourceTree tree, final IFolder source,
-			final IFolder destination, final int updateFlags,
+	public boolean moveFolder(final IResourceTree tree, final IFolder srcf,
+			final IFolder dstf, final int updateFlags,
 			final IProgressMonitor monitor) {
-		// TODO: Implement this. Should be relatively easy, but consider that
-		// Eclipse thinks folders are real thinsgs, while Git does not care.
-		return FINISH_FOR_ME;
+		final boolean force = (updateFlags & IResource.FORCE) == IResource.FORCE;
+		if (!force && !tree.isSynchronized(srcf, IResource.DEPTH_ZERO))
+			return false;
+
+		final RepositoryMapping srcm = RepositoryMapping.getMapping(srcf);
+		if (srcm == null)
+			return false;
+		final RepositoryMapping dstm = RepositoryMapping.getMapping(dstf);
+
+		try {
+			final DirCache sCache = DirCache.lock(srcm.getRepository());
+			final String sPath = srcm.getRepoRelativePath(srcf);
+			final DirCacheEntry[] sEnt = sCache.getEntriesWithin(sPath);
+			if (sEnt.length == 0) {
+				sCache.unlock();
+				return false;
+			}
+
+			final DirCacheEditor sEdit = sCache.editor();
+			sEdit.add(new DirCacheEditor.DeleteTree(sPath));
+			if (dstm != null && dstm.getRepository() == srcm.getRepository()) {
+				final String dPath = srcm.getRepoRelativePath(dstf) + "/";
+				final int sPathLen = sPath.length() + 1;
+				for (final DirCacheEntry se : sEnt) {
+					final String p = se.getPathString().substring(sPathLen);
+					sEdit.add(new DirCacheEditor.PathEdit(dPath + p) {
+						@Override
+						public void apply(final DirCacheEntry dEnt) {
+							dEnt.copyMetaData(se);
+						}
+					});
+				}
+			}
+			if (!sEdit.commit())
+				tree.failed(new Status(IStatus.ERROR, Activator.getPluginId(),
+						0, CoreText.MoveDeleteHook_operationError, null));
+
+			tree.standardMoveFolder(srcf, dstf, updateFlags, monitor);
+		} catch (IOException e) {
+			tree.failed(new Status(IStatus.ERROR, Activator.getPluginId(), 0,
+					CoreText.MoveDeleteHook_operationError, e));
+		}
+		return true;
 	}
 
 	public boolean moveProject(final IResourceTree tree, final IProject source,
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 25/26] Rewrite UntrackOperation to use DirCacheBuilder
  2008-08-12  1:08                                               ` [EGIT PATCH 24/26] Teach GitMoveDeleteHook how to move a folder recursively Shawn O. Pearce
@ 2008-08-12  1:08                                                 ` Shawn O. Pearce
  2008-08-12  1:08                                                   ` [EGIT PATCH 26/26] Rewrite AssumeUnchangedOperation to use DirCache Shawn O. Pearce
  0 siblings, 1 reply; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

We can more efficiently untrack single files and entire directories using
the DirCacheBuilder API as that API is able to perform range edits on
the index data structure.  Its also able to perform faster disk IO, due
to less conversion overheads.

Although performance of untrack is not a critical feature it is a good
idea to move it over to the newer API, as we will eventually get rid of
the GitIndex class entirely.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../org/spearce/egit/core/op/UntrackOperation.java |   98 +++++++++++---------
 1 files changed, 55 insertions(+), 43 deletions(-)

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/op/UntrackOperation.java b/org.spearce.egit.core/src/org/spearce/egit/core/op/UntrackOperation.java
index b491e6d..4656552 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/op/UntrackOperation.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/op/UntrackOperation.java
@@ -1,6 +1,7 @@
 /*******************************************************************************
  * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Google Inc.
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
@@ -8,15 +9,14 @@
  *******************************************************************************/
 package org.spearce.egit.core.op;
 
-import java.io.File;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
+import java.util.Map;
 
 import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IResourceVisitor;
 import org.eclipse.core.resources.IWorkspaceRunnable;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IAdaptable;
@@ -26,7 +26,9 @@ import org.spearce.egit.core.Activator;
 import org.spearce.egit.core.CoreText;
 import org.spearce.egit.core.project.GitProjectData;
 import org.spearce.egit.core.project.RepositoryMapping;
-import org.spearce.jgit.lib.GitIndex;
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheEditor;
+import org.spearce.jgit.lib.Repository;
 
 /**
  * Remove one or more existing files/folders from the Git repository.
@@ -43,6 +45,10 @@ import org.spearce.jgit.lib.GitIndex;
 public class UntrackOperation implements IWorkspaceRunnable {
 	private final Collection rsrcList;
 
+	private final IdentityHashMap<Repository, DirCacheEditor> edits;
+
+	private final IdentityHashMap<RepositoryMapping, Object> mappings;
+
 	/**
 	 * Create a new operation to stop tracking existing files/folders.
 	 * 
@@ -52,63 +58,69 @@ public class UntrackOperation implements IWorkspaceRunnable {
 	 */
 	public UntrackOperation(final Collection rsrcs) {
 		rsrcList = rsrcs;
+		edits = new IdentityHashMap<Repository, DirCacheEditor>();
+		mappings = new IdentityHashMap<RepositoryMapping, Object>();
 	}
 
 	public void run(IProgressMonitor m) throws CoreException {
-		if (m == null) {
+		if (m == null)
 			m = new NullProgressMonitor();
-		}
 
-		final IdentityHashMap<RepositoryMapping, Boolean> tomerge = new IdentityHashMap<RepositoryMapping, Boolean>();
+		edits.clear();
+		mappings.clear();
+
 		m.beginTask(CoreText.AddOperation_adding, rsrcList.size() * 200);
 		try {
 			for (Object obj : rsrcList) {
-				obj = ((IAdaptable)obj).getAdapter(IResource.class);
-				if (obj instanceof IResource) {
-					final IResource toRemove = (IResource)obj;
-					final GitProjectData pd = GitProjectData.get(toRemove.getProject());
-					final RepositoryMapping rm = pd.getRepositoryMapping(toRemove);
-					final GitIndex index = rm.getRepository().getIndex();
-					tomerge.put(rm, Boolean.TRUE);
-					if (toRemove instanceof IContainer) {
-						((IContainer)toRemove).accept(new IResourceVisitor() {
-							public boolean visit(IResource resource) throws CoreException {
-								if (resource.getType() == IResource.FILE) {
-									index.remove(rm.getWorkDir(), new File(rm.getWorkDir(),rm.getRepoRelativePath(resource)));
-								}
-								return true;
-							}
-						},IResource.DEPTH_INFINITE, IContainer.EXCLUDE_DERIVED);
-					} else {
-						index.remove(rm.getWorkDir(), new File(rm.getWorkDir(),rm.getRepoRelativePath(toRemove)));
-					}
-				}
+				obj = ((IAdaptable) obj).getAdapter(IResource.class);
+				if (obj instanceof IResource)
+					remove((IResource) obj);
 				m.worked(200);
 			}
-			for (RepositoryMapping rm : tomerge.keySet()) {
-				m.setTaskName("Writing index for "+rm.getRepository().getDirectory());
-				rm.getRepository().getIndex().write();
+
+			for (Map.Entry<Repository, DirCacheEditor> e : edits.entrySet()) {
+				final Repository db = e.getKey();
+				final DirCacheEditor editor = e.getValue();
+				m.setTaskName("Writing index for " + db.getDirectory());
+				editor.commit();
 			}
 		} catch (RuntimeException e) {
-			e.printStackTrace();
 			throw Activator.error(CoreText.UntrackOperation_failed, e);
 		} catch (IOException e) {
-			e.printStackTrace();
 			throw Activator.error(CoreText.UntrackOperation_failed, e);
 		} finally {
+			for (final RepositoryMapping rm : mappings.keySet())
+				rm.fireRepositoryChanged();
+			edits.clear();
+			mappings.clear();
+			m.done();
+		}
+	}
+
+	private void remove(final IResource path) throws CoreException {
+		final IProject proj = path.getProject();
+		final GitProjectData pd = GitProjectData.get(proj);
+		if (pd == null)
+			return;
+		final RepositoryMapping rm = pd.getRepositoryMapping(path);
+		if (rm == null)
+			return;
+		final Repository db = rm.getRepository();
+
+		DirCacheEditor e = edits.get(db);
+		if (e == null) {
 			try {
-				final Iterator i = tomerge.keySet().iterator();
-				while (i.hasNext()) {
-					final RepositoryMapping r = (RepositoryMapping) i.next();
-					r.getRepository().getIndex().read();
-					r.fireRepositoryChanged();
-				}
-			} catch (IOException e) {
-				e.printStackTrace();
-			} finally {
-				m.done();
+				e = DirCache.lock(db).editor();
+			} catch (IOException err) {
+				throw Activator.error(CoreText.UntrackOperation_failed, err);
 			}
+			edits.put(db, e);
+			mappings.put(rm, rm);
 		}
-	}
 
+		if (path instanceof IContainer)
+			e.add(new DirCacheEditor.DeleteTree(rm.getRepoRelativePath(path)));
+		else
+			e.add(new DirCacheEditor.DeletePath(rm.getRepoRelativePath(path)));
+	}
 }
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [EGIT PATCH 26/26] Rewrite AssumeUnchangedOperation to use DirCache
  2008-08-12  1:08                                                 ` [EGIT PATCH 25/26] Rewrite UntrackOperation to use DirCacheBuilder Shawn O. Pearce
@ 2008-08-12  1:08                                                   ` Shawn O. Pearce
  0 siblings, 0 replies; 26+ messages in thread
From: Shawn O. Pearce @ 2008-08-12  1:08 UTC (permalink / raw)
  To: Robin Rosenberg; +Cc: git

Its faster to use the DirCache and run over the range of entries
than to use GitIndex and probe the hash many times as we are in
a loop over the resources in the workspace.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
 .../egit/core/op/AssumeUnchangedOperation.java     |  133 ++++++++++----------
 1 files changed, 69 insertions(+), 64 deletions(-)

diff --git a/org.spearce.egit.core/src/org/spearce/egit/core/op/AssumeUnchangedOperation.java b/org.spearce.egit.core/src/org/spearce/egit/core/op/AssumeUnchangedOperation.java
index 78a84bb..fa70b6c 100644
--- a/org.spearce.egit.core/src/org/spearce/egit/core/op/AssumeUnchangedOperation.java
+++ b/org.spearce.egit.core/src/org/spearce/egit/core/op/AssumeUnchangedOperation.java
@@ -1,6 +1,7 @@
 /*******************************************************************************
  * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2008, Google Inc.
  *
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
@@ -11,11 +12,11 @@ package org.spearce.egit.core.op;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
+import java.util.Map;
 
 import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IResourceVisitor;
 import org.eclipse.core.resources.IWorkspaceRunnable;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IAdaptable;
@@ -23,9 +24,11 @@ import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.NullProgressMonitor;
 import org.spearce.egit.core.Activator;
 import org.spearce.egit.core.CoreText;
+import org.spearce.egit.core.project.GitProjectData;
 import org.spearce.egit.core.project.RepositoryMapping;
-import org.spearce.jgit.lib.GitIndex;
-import org.spearce.jgit.lib.GitIndex.Entry;
+import org.spearce.jgit.dircache.DirCache;
+import org.spearce.jgit.dircache.DirCacheEntry;
+import org.spearce.jgit.lib.Repository;
 
 /**
  * Tell JGit to ignore changes in selected files
@@ -33,87 +36,89 @@ import org.spearce.jgit.lib.GitIndex.Entry;
 public class AssumeUnchangedOperation implements IWorkspaceRunnable {
 	private final Collection rsrcList;
 
+	private final IdentityHashMap<Repository, DirCache> caches;
+
+	private final IdentityHashMap<RepositoryMapping, Object> mappings;
+
 	/**
 	 * Create a new operation to ignore changes in tracked files
-	 *
+	 * 
 	 * @param rsrcs
-	 *            collection of {@link IResource}s which should be 
-	 *            ignored when looking for changes or committing.
+	 *            collection of {@link IResource}s which should be ignored when
+	 *            looking for changes or committing.
 	 */
 	public AssumeUnchangedOperation(final Collection rsrcs) {
 		rsrcList = rsrcs;
+		caches = new IdentityHashMap<Repository, DirCache>();
+		mappings = new IdentityHashMap<RepositoryMapping, Object>();
 	}
 
 	public void run(IProgressMonitor m) throws CoreException {
-		if (m == null) {
+		if (m == null)
 			m = new NullProgressMonitor();
-		}
 
-		final IdentityHashMap<RepositoryMapping, Boolean> tomerge = new IdentityHashMap<RepositoryMapping, Boolean>();
-		m.beginTask(CoreText.AssumeUnchangedOperation_adding, rsrcList.size() * 200);
+		caches.clear();
+		mappings.clear();
+
+		m.beginTask(CoreText.AssumeUnchangedOperation_adding,
+				rsrcList.size() * 200);
 		try {
 			for (Object obj : rsrcList) {
-				obj = ((IAdaptable)obj).getAdapter(IResource.class);
-				if (obj instanceof IResource) {
-					final IResource toAssumeValid = (IResource)obj;
-					final RepositoryMapping rm = RepositoryMapping.getMapping(toAssumeValid);
-					final GitIndex index = rm.getRepository().getIndex();
-
-					if (toAssumeValid instanceof IContainer) {
-						((IContainer)toAssumeValid).accept(new IResourceVisitor() {
-							public boolean visit(IResource resource) throws CoreException {
-								try {
-									String repoPath = rm.getRepoRelativePath(resource);
-									Entry entry = index.getEntry(repoPath);
-									if (entry != null) {
-										if (!entry.isAssumedValid()) {
-											entry.setAssumeValid(true);
-											tomerge.put(rm, Boolean.TRUE);
-										}
-									}
-								} catch (IOException e) {
-									e.printStackTrace();
-									throw Activator.error(CoreText.AssumeUnchangedOperation_failed, e);
-								}
-								return true;
-							}
-						},IResource.DEPTH_INFINITE, IContainer.EXCLUDE_DERIVED);
-					} else {
-						String repoPath = rm.getRepoRelativePath((IResource) obj);
-						Entry entry = index.getEntry(repoPath);
-						if (entry != null) {
-							if (!entry.isAssumedValid()) {
-								entry.setAssumeValid(true);
-								tomerge.put(rm, Boolean.TRUE);
-							}
-						}
-					}
-				}
+				obj = ((IAdaptable) obj).getAdapter(IResource.class);
+				if (obj instanceof IResource)
+					assumeValid((IResource) obj);
 				m.worked(200);
 			}
-			for (RepositoryMapping rm : tomerge.keySet()) {
-				m.setTaskName("Writing index for "+rm.getRepository().getDirectory());
-				rm.getRepository().getIndex().write();
+
+			for (Map.Entry<Repository, DirCache> e : caches.entrySet()) {
+				final Repository db = e.getKey();
+				final DirCache editor = e.getValue();
+				m.setTaskName("Writing index for " + db.getDirectory());
+				editor.write();
+				editor.commit();
 			}
 		} catch (RuntimeException e) {
-			e.printStackTrace();
-			throw Activator.error(CoreText.AssumeUnchangedOperation_failed, e);
+			throw Activator.error(CoreText.UntrackOperation_failed, e);
 		} catch (IOException e) {
-			e.printStackTrace();
-			throw Activator.error(CoreText.AssumeUnchangedOperation_failed, e);
+			throw Activator.error(CoreText.UntrackOperation_failed, e);
 		} finally {
+			for (final RepositoryMapping rm : mappings.keySet())
+				rm.fireRepositoryChanged();
+			caches.clear();
+			mappings.clear();
+			m.done();
+		}
+	}
+
+	private void assumeValid(final IResource resource) throws CoreException {
+		final IProject proj = resource.getProject();
+		final GitProjectData pd = GitProjectData.get(proj);
+		if (pd == null)
+			return;
+		final RepositoryMapping rm = pd.getRepositoryMapping(resource);
+		if (rm == null)
+			return;
+		final Repository db = rm.getRepository();
+
+		DirCache cache = caches.get(db);
+		if (cache == null) {
 			try {
-				final Iterator i = tomerge.keySet().iterator();
-				while (i.hasNext()) {
-					final RepositoryMapping r = (RepositoryMapping) i.next();
-					r.getRepository().getIndex().read();
-					r.fireRepositoryChanged();
-				}
-			} catch (IOException e) {
-				e.printStackTrace();
-			} finally {
-				m.done();
+				cache = DirCache.lock(db);
+			} catch (IOException err) {
+				throw Activator.error(CoreText.UntrackOperation_failed, err);
 			}
+			caches.put(db, cache);
+			mappings.put(rm, rm);
+		}
+
+		final String path = rm.getRepoRelativePath(resource);
+		if (resource instanceof IContainer) {
+			for (final DirCacheEntry ent : cache.getEntriesWithin(path))
+				ent.setAssumeValid(true);
+		} else {
+			final DirCacheEntry ent = cache.getEntry(path);
+			if (ent != null)
+				ent.setAssumeValid(true);
 		}
 	}
 }
-- 
1.6.0.rc2.22.g71b99

^ permalink raw reply related	[flat|nested] 26+ messages in thread

end of thread, other threads:[~2008-08-12  1:11 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-08-12  1:07 [EGIT PATCH 00/26] New DirCache API Shawn O. Pearce
2008-08-12  1:07 ` [EGIT PATCH 01/26] Force all source code to UTF-8 encoding by default Shawn O. Pearce
2008-08-12  1:07   ` [EGIT PATCH 02/26] Protect WorkingTreeIterator's name encoding from weird ByteBuffers Shawn O. Pearce
2008-08-12  1:07     ` [EGIT PATCH 03/26] Add Constants.encode as a utility for quick encoding in UTF-8 Shawn O. Pearce
2008-08-12  1:07       ` [EGIT PATCH 04/26] Rely upon Constants.CHARSET over Constants.CHARACTER_ENCODING Shawn O. Pearce
2008-08-12  1:07         ` [EGIT PATCH 05/26] Allow AbstractTreeIterators to find out about StopWalkExceptions Shawn O. Pearce
2008-08-12  1:07           ` [EGIT PATCH 06/26] Implement a new .git/index (aka dircache) read interface Shawn O. Pearce
2008-08-12  1:07             ` [EGIT PATCH 07/26] Export the new DirCache API to Eclipse plugins using jgit Shawn O. Pearce
2008-08-12  1:07               ` [EGIT PATCH 08/26] Support locking (and unlocking) a .git/index through DirCache Shawn O. Pearce
2008-08-12  1:07                 ` [EGIT PATCH 09/26] Support writing " Shawn O. Pearce
2008-08-12  1:07                   ` [EGIT PATCH 10/26] Support the 'TREE' extension in " Shawn O. Pearce
2008-08-12  1:07                     ` [EGIT PATCH 11/26] Support using a DirCache within a TreeWalk Shawn O. Pearce
2008-08-12  1:07                       ` [EGIT PATCH 12/26] Support recreating a .git/index through DirCache Shawn O. Pearce
2008-08-12  1:08                         ` [EGIT PATCH 13/26] Support iterating and building a DirCache at the same time Shawn O. Pearce
2008-08-12  1:08                           ` [EGIT PATCH 14/26] Support creating a new DirCacheEntry for an arbitrary path Shawn O. Pearce
2008-08-12  1:08                             ` [EGIT PATCH 15/26] Support a simplified model of editing index entries Shawn O. Pearce
2008-08-12  1:08                               ` [EGIT PATCH 16/26] Support recursively getting all entries under a subtree path Shawn O. Pearce
2008-08-12  1:08                                 ` [EGIT PATCH 17/26] Support copying meta fields from one DirCacheEntry to another Shawn O. Pearce
2008-08-12  1:08                                   ` [EGIT PATCH 18/26] Add JUnit tests for new DirCache API Shawn O. Pearce
     [not found]                                     ` <1218503293-14057-20-git-send-email-spearce@spearce.org>
2008-08-12  1:08                                       ` [EGIT PATCH 20/26] Allow the new DirCacheIterator in command line arguments Shawn O. Pearce
2008-08-12  1:08                                         ` [EGIT PATCH 21/26] Add debugging commands to interact with the new DirCache code Shawn O. Pearce
2008-08-12  1:08                                           ` [EGIT PATCH 22/26] Add a basic command line implementation of rm Shawn O. Pearce
2008-08-12  1:08                                             ` [EGIT PATCH 23/26] Rewrite GitMoveDeleteHook to use DirCacheBuilder Shawn O. Pearce
2008-08-12  1:08                                               ` [EGIT PATCH 24/26] Teach GitMoveDeleteHook how to move a folder recursively Shawn O. Pearce
2008-08-12  1:08                                                 ` [EGIT PATCH 25/26] Rewrite UntrackOperation to use DirCacheBuilder Shawn O. Pearce
2008-08-12  1:08                                                   ` [EGIT PATCH 26/26] Rewrite AssumeUnchangedOperation to use DirCache Shawn O. Pearce

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).