* [JGIT PATCH 2/2] Update the reflog for HEAD when the referenced branch is modified
From: Robin Rosenberg @ 2009-08-16 21:36 UTC (permalink / raw)
To: spearce; +Cc: git, Robin Rosenberg
In-Reply-To: <1250458612-21095-1-git-send-email-robin.rosenberg@dewire.com>
This omission was mentioned in http://code.google.com/p/egit/issues/detail?id=5
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
---
.../tst/org/spearce/jgit/lib/RefUpdateTest.java | 32 ++++++++++++++++++++
.../src/org/spearce/jgit/lib/RefLogWriter.java | 6 ++-
.../src/org/spearce/jgit/lib/RefRename.java | 6 +++-
.../src/org/spearce/jgit/lib/RefUpdate.java | 11 ++++++-
4 files changed, 51 insertions(+), 4 deletions(-)
diff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RefUpdateTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RefUpdateTest.java
index 655e54e..edccf37 100644
--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RefUpdateTest.java
+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RefUpdateTest.java
@@ -82,6 +82,15 @@ public void testNoCacheObjectIdSubclass() throws IOException {
assertNotSame(newid, r.getObjectId());
assertSame(ObjectId.class, r.getObjectId().getClass());
assertEquals(newid.copy(), r.getObjectId());
+ List<org.spearce.jgit.lib.ReflogReader.Entry> reverseEntries1 = db.getReflogReader("refs/heads/abc").getReverseEntries();
+ org.spearce.jgit.lib.ReflogReader.Entry entry1 = reverseEntries1.get(0);
+ assertEquals(1, reverseEntries1.size());
+ assertEquals(ObjectId.zeroId(), entry1.getOldId());
+ assertEquals(r.getObjectId(), entry1.getNewId());
+ assertEquals(new PersonIdent(db).toString(), entry1.getWho().toString());
+ assertEquals("", entry1.getComment());
+ List<org.spearce.jgit.lib.ReflogReader.Entry> reverseEntries2 = db.getReflogReader("HEAD").getReverseEntries();
+ assertEquals(0, reverseEntries2.size());
}
public void testNewNamespaceConflictWithLoosePrefixNameExists()
@@ -103,6 +112,8 @@ public void testNewNamespaceConflictWithLoosePrefixNameExists()
ru.setNewObjectId(newid2);
Result update2 = ru2.update();
assertEquals(Result.LOCK_FAILURE, update2);
+ assertEquals(1, db.getReflogReader("refs/heads/z").getReverseEntries().size());
+ assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
}
public void testNewNamespaceConflictWithPackedPrefixNameExists()
@@ -115,6 +126,8 @@ public void testNewNamespaceConflictWithPackedPrefixNameExists()
ru.setNewObjectId(newid);
Result update = ru.update();
assertEquals(Result.LOCK_FAILURE, update);
+ assertNull(db.getReflogReader("refs/heads/master/x"));
+ assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
}
public void testNewNamespaceConflictWithLoosePrefixOfExisting()
@@ -136,6 +149,9 @@ public void testNewNamespaceConflictWithLoosePrefixOfExisting()
ru.setNewObjectId(newid2);
Result update2 = ru2.update();
assertEquals(Result.LOCK_FAILURE, update2);
+ assertEquals(1, db.getReflogReader("refs/heads/z/a").getReverseEntries().size());
+ assertNull(db.getReflogReader("refs/heads/z"));
+ assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
}
public void testNewNamespaceConflictWithPackedPrefixOfExisting()
@@ -148,6 +164,8 @@ public void testNewNamespaceConflictWithPackedPrefixOfExisting()
ru.setNewObjectId(newid);
Result update = ru.update();
assertEquals(Result.LOCK_FAILURE, update);
+ assertNull(db.getReflogReader("refs/heads/prefix"));
+ assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
}
/**
@@ -167,6 +185,8 @@ public void testDeleteHEADreferencedRef() throws IOException {
Result delete = updateRef2.delete();
assertEquals(Result.REJECTED_CURRENT_BRANCH, delete);
assertEquals(pid, db.resolve("refs/heads/master"));
+ assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size());
+ assertEquals(0,db.getReflogReader("HEAD").getReverseEntries().size());
}
public void testLooseDelete() throws IOException {
@@ -175,11 +195,14 @@ public void testLooseDelete() throws IOException {
ref.update(); // create loose ref
ref = updateRef(newRef); // refresh
delete(ref, Result.NO_CHANGE);
+ assertNull(db.getReflogReader("refs/heads/abc"));
}
public void testDeleteHead() throws IOException {
final RefUpdate ref = updateRef(Constants.HEAD);
delete(ref, Result.REJECTED_CURRENT_BRANCH, true, false);
+ assertEquals(0, db.getReflogReader("refs/heads/master").getReverseEntries().size());
+ assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
}
/**
@@ -498,6 +521,10 @@ public void testRenameBranchAlsoInPack() throws IOException {
assertNull(db.resolve("refs/heads/b"));
assertEquals("Branch: renamed b to new/name", db.getReflogReader(
"new/name").getLastEntry().getComment());
+ assertEquals(3, db.getReflogReader("refs/heads/new/name").getReverseEntries().size());
+ assertEquals("Branch: renamed b to new/name", db.getReflogReader("refs/heads/new/name").getReverseEntries().get(0).getComment());
+ assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size());
+ // make sure b's log file is gone too.
assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists());
// Create new Repository instance, to reread caches and make sure our
@@ -640,6 +667,9 @@ public void testRenameRefNameColission1avoided() throws IOException {
.getReverseEntries().get(1).getComment());
assertEquals("Setup", db.getReflogReader("a/b").getReverseEntries()
.get(2).getComment());
+ // same thing was logged to HEAD
+ assertEquals("Branch: renamed a to a/b", db.getReflogReader("HEAD")
+ .getReverseEntries().get(0).getComment());
}
public void testRenameRefNameColission2avoided() throws IOException {
@@ -673,5 +703,7 @@ public void testRenameRefNameColission2avoided() throws IOException {
.getReverseEntries().get(1).getComment());
assertEquals("Setup", db.getReflogReader("prefix").getReverseEntries()
.get(2).getComment());
+ assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
+ "HEAD").getReverseEntries().get(0).getComment());
}
}
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefLogWriter.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefLogWriter.java
index 0864209..4aad809 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefLogWriter.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefLogWriter.java
@@ -56,13 +56,15 @@ static void append(final RefUpdate u, final String msg) throws IOException {
final PersonIdent ident = u.getRefLogIdent();
appendOneRecord(oldId, newId, ident, msg, db, u.getName());
+ if (!u.getName().equals(u.getOrigName()))
+ appendOneRecord(oldId, newId, ident, msg, db, u.getOrigName());
}
- static void append(RefRename refRename, String msg) throws IOException {
+ static void append(RefRename refRename, String logName, String msg) throws IOException {
final ObjectId id = refRename.getObjectId();
final Repository db = refRename.getRepository();
final PersonIdent ident = refRename.getRefLogIdent();
- appendOneRecord(id, id, ident, msg, db, refRename.getToName());
+ appendOneRecord(id, id, ident, msg, db, logName);
}
static void renameTo(final Repository db, final RefUpdate from,
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefRename.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefRename.java
index c89459b..0718620 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefRename.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefRename.java
@@ -136,9 +136,13 @@ public Result rename() throws IOException {
} else {
db.fireRefsMaybeChanged();
}
- RefLogWriter.append(this, "Branch: renamed "
+ RefLogWriter.append(this, newToUpdate.getName(), "Branch: renamed "
+ db.shortenRefName(oldFromDelete.getName()) + " to "
+ db.shortenRefName(newToUpdate.getName()));
+ if (renameHEADtoo)
+ RefLogWriter.append(this, Constants.HEAD, "Branch: renamed "
+ + db.shortenRefName(oldFromDelete.getName()) + " to "
+ + db.shortenRefName(newToUpdate.getName()));
return renameResult = Result.RENAMED;
} catch (RuntimeException e) {
throw e;
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java
index f8ecc3c..69399ec 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RefUpdate.java
@@ -182,13 +182,22 @@ public Repository getRepository() {
/**
* Get the name of the ref this update will operate on.
*
- * @return name of this ref.
+ * @return name of underlying ref.
*/
public String getName() {
return ref.getName();
}
/**
+ * Get the requested name of the ref thit update will operate on
+ *
+ * @return original (requested) name of the underlying ref.
+ */
+ public String getOrigName() {
+ return ref.getOrigName();
+ }
+
+ /**
* Get the new value the ref will be (or was) updated to.
*
* @return new value. Null if the caller has not configured it.
--
1.6.4.115.gc0eb0
^ permalink raw reply related
* Re: GitHub linking, was Re: bbchop & Wikipedia's Bayesian search theory page
From: Scott Chacon @ 2009-08-16 21:48 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: git, Ealdwulf Wuffinga
In-Reply-To: <alpine.DEB.1.00.0908161915040.8306@pacific.mpi-cbg.de>
Hey,
On Sun, Aug 16, 2009 at 10:18 AM, Johannes
Schindelin<Johannes.Schindelin@gmx.de> wrote:
> Hi,
>
> The issue is that the link incorrectly includes the closing parenthesis.
> It should link to
>
> http://en.wikipedia.org/wiki/Bayesian_search_theory
>
> not
>
> http://en.wikipedia.org/wiki/Bayesian_search_theory)
>
> Scott, is it possible to fix that? Or is the README not magically made
> from the README in the repository (which does not contain HTML markup)?
>
If they change the README to have spaces between the url and the
parens, it will link properly - I'll file a bug for the linking issue.
Thanks.
Scott
^ permalink raw reply
* Re: Linus' sha1 is much faster!
From: Giuseppe Scrivano @ 2009-08-16 22:15 UTC (permalink / raw)
To: Linus Torvalds; +Cc: Pádraig Brady, Bug-coreutils, Git Mailing List
In-Reply-To: <alpine.LFD.2.01.0908161306340.3162@localhost.localdomain>
Linus Torvalds <torvalds@linux-foundation.org> writes:
> I pretty much can guarantee you that it improves things only because it
> makes gcc generate crap code, which then hides some of the P4 issues.
>
> I'd also suggest you try gcc-4.4, since that apparently fixes some of the
> oddest spill issues.
Thanks for the hint. I tried gcc-4.4 and it produces slower code than
4.3 on the gnulib SHA1 implementation and my patch makes it even more!
I noticed that on my machine your implementation is ~30-40% faster using
SHA_ROT for rol/ror instructions than inline assembly, at least with the
test-case Pádraig wrote. Am I the only one reporting it?
Cheers,
Giuseppe
^ permalink raw reply
* Re: [PATCH 1/6 (v2)] revision caching documentation: man page and technical discussion
From: Nick Edelen @ 2009-08-16 22:41 UTC (permalink / raw)
To: Nick Edelen, Junio C Hamano, Nicolas Pitre, Johannes Schindelin,
Sam Vilain
In-Reply-To: <op.uyb1uuu4tdk399@sirnot.private>
Before any code is introduced the full documentation is put forth. This
provides a man page for the porcelain, and a technical doc in technical/. The
latter describes the API, and discusses rev-cache's design, file format and
mechanics.
Signed-off-by: Nick Edelen <sirnot@gmail.com>
---
Documentation/git-rev-cache.txt | 144 ++++++++
Documentation/technical/rev-cache.txt | 614 +++++++++++++++++++++++++++++++++
2 files changed, 758 insertions(+), 0 deletions(-)
diff --git a/Documentation/git-rev-cache.txt b/Documentation/git-rev-cache.txt
new file mode 100644
index 0000000..3479499
--- /dev/null
+++ b/Documentation/git-rev-cache.txt
@@ -0,0 +1,144 @@
+git-rev-cache(1)
+================
+
+NAME
+----
+git-rev-cache - Add, walk and maintain revision cache slices
+
+SYNOPSIS
+--------
+'git-rev-cache' COMMAND [options] [<commit>...]
+
+DESCRIPTION
+-----------
+The revision cache ('rev-cache') provides a mechanism for significantly
+speeding up revision traversals. It does this by creating an efficient
+database (cache) of commits, their related objects and topological relations.
+Independant of packs and the object store, this database is composed of
+rev-cache "slices" -- each a different file storing a given segment of commit
+history. To map commits to their respective slices, a single index file is
+kept for the rev-cache.
+
+'git-rev-cache' provides a front-end for the rev-cache mechanism, intended for
+updating and maintaining rev-cache slices in the current repository. New cache
+slice files can be 'add'ed, to keep the cache up-to-date; individual slices can
+be traversed; smaller slices can be 'fuse'd into a larger slice; and the
+rev-cache index can be regenerated.
+
+COMMANDS
+--------
+
+add
+~~~
+Add revisions to the cache by creating a new cache slice. Reads a revision
+list from the command line, formatted as: `START START ... \--not END END ...`
+
+Options:
+
+\--all::
+ Include all refs in the new cache slice, like the \--all option in
+ 'rev-list'.
+
+\--fresh::
+ Exclude everything already in the revision cache, analogous to
+ \--incremental in 'pack-objects'.
+
+\--stdin::
+ Read newline-seperated revisions from the standard input. Use \--not
+ to exclude commits, as on the command line.
+
+\--legs::
+ Ensure newly-generated cache slice has no partial ends. This means that
+ no commit has partially cached parents, in that all its parents are
+ cached or none of them are.
++
+\--legs will cause 'rev-cache' to expand potential slice end-points (creating
+"legs") until this condition is met, simplifying the cache slice structure.
+'rev-cache' itself does not care if a slice has legs or not, but the condition
+may reduce the required complexity of other applications that might use the
+revision cache.
+
+\--no-objects::
+ Non-commit objects are normally included along with the commit with
+ which they were introduced. This is obviously very benificial, but can
+ take longer in cache slice generation. Using this option will disable
+ non-commit object caching.
++
+\--no-objects is mainly intended for debugging or development purposes, but may
+find use in special situations (e.g. common traversal of only commits).
+
+walk
+~~~~
+Analogous to a slice-oriented 'rev-list', 'walk' will traverse a region in a
+particular cache slice. Interesting and uninteresting (delimited, as with
+'rev-list', with \--not) are specified on the command line, and output is the
+same as vanilla 'rev-list'.
+
+Options:
+
+\--objects::
+ Like 'rev-list', 'walk' will normally only list commits. Use this
+ option to list non-commit objects as well, if they are present in the
+ cache slice.
+
+fuse
+~~~~
+Merge several cache slices into a single large slice, like 'repack' for
+'rev-cache'. On each invocation of 'add' a new file ("slice") is added to the
+revision cache directory, and after several additions the directory may become
+populated with many, relatively small slices. Numerous smaller slices will
+yield poorer performance than a one or two large ones, because of the overhead
+of loading new slices into memory.
+
+Running 'fuse' every once in a while will solve this problem by coalescing all
+the cache slices into one larger slice. For very large projects, using
+\--ignore-size is advisable to prevent overly large cache slices. Setting git
+'config' option 'gc.revcache' to 1 will enable cache slice fusion upon garbage
+collection.
+
+Note that 'fuse' uses the internal revision walker, so the options used in
+fusion override those of the cache slices upon which it operates. For example,
+if some slices were generated with \--no-objects, yet 'fuse' was performed with
+non-commit objects, the resulting slice would still contain objects but would
+take longer to generate.
+
+Options:
+
+\--all::
+ Normally fuse will only include everything that's already in the
+ revision cache. \--add tells it to start walking from the branch
+ heads, effectively an `add --all --fresh; fuse` (pseudo-command).
+
+\--no-objects::
+ As in 'add', this option disables inclusion of non-commit objects. If
+ some cache slices do contain such objects, the information will be lost.
+
+\--ignore-size[=N]::
+ Do not merge cache slices of size >=N (be aware that slices must be
+ mapped to memory). N can have a suffix of "k" or "m", denoting N as
+ kilobytes and megabytes, respectively. If N is not provided 'fuse'
+ will default to a size of ~25MB.
+
+index
+~~~~~
+Regenerate the revision cache index. If the rev-cache index file associating
+objects with cache slices gets corrupted, lost, or otherwise becomes unusable,
+'index' will quickly regenerate the file. It's most likely that this won't be
+needed in every day use, as it is targeted towards debugging and development.
+
+alt
+~~~
+Create a cache slice pointer to another slice, identified by its full path:
+`fuse path/to/other/slice`
+
+This command is useful if you have several repositories sharing a common
+history. Although space requirements for rev-cache are slim anyway, you can in
+this situation reduce it further by using slice pointers, pointing to relavant
+slices in other repositories. Note that only one level of redirection is
+allowed, and the slice pointer will break if the original slice is removed.
+'fuse' will not touch slice pointers.
+
+DISCUSSION
+----------
+For an explanation of the API and its inner workings, see
+link:technical/rev-cache.txt[technical info on rev-cache].
diff --git a/Documentation/technical/rev-cache.txt b/Documentation/technical/rev-cache.txt
new file mode 100644
index 0000000..6e7c7f6
--- /dev/null
+++ b/Documentation/technical/rev-cache.txt
@@ -0,0 +1,614 @@
+rev-cache
+=========
+
+The revision cache API ('rev-cache') provides a method for efficiently storing
+and accessing commit branch sections. Such branch slices are defined by a
+series of start/top (interesting) and end/bottom (uninteresting) commits. Each
+slice contains information on commits in topological order. Recorded with each
+commit is:
+
+* All intra-slice topological relations, encoded into path "channels" (see
+ 'Mechanics' for full explanation).
+* Object meta-data: type, SHA-1, size, date (for commits).
+* Objects introduced by that commit, not present in the its cached parents.
+
+In addition to the API, basic structures are exported for the possibility of
+direct access.
+
+The API
+-------
+You can find the function prototypes in `revision.h`.
+
+Data Structures
+~~~~~~~~~~~~~~~
+The `rev_cache_info` struct holds all the options and flags for the API.
+
+----
+struct rev_cache_info {
+ /* generation flags */
+ unsigned objects : 1,
+ legs : 1,
+ make_index : 1,
+ fuse_me : 1;
+
+ /* index inclusion */
+ unsigned overwrite_all : 1;
+
+ /* traversal flags */
+ unsigned add_to_pending : 1;
+
+ /* fuse options */
+ unsigned int ignore_size;
+
+ /* reserved */
+ struct rev_cache_slice_map *maps,
+ *last_map;
+};
+----
+
+The fields:
+
+`objects`::
+ Add non-commit objects to slice.
+
+`legs`::
+ Ensure end/bottom commits have no children.
+
+`make_index`::
+ Integrate newly-made slice into index.
+
+`fuse_me`::
+ This is specified if a fuse is occuring, and slices are to be reused.
+ This option requires `maps` and `last_maps` to be initialized.
+
+`overwrite_all`::
+ When a cache slice is added to the index, sometimes overlap occures
+ between it and other slices. Normally, original index entries are kept
+ unless the new entry represents a start commit (older entries are more
+ likely to lead to greater in-slice traversals). This options overrides
+ that, and updates all entries of the new slice.
+
+`add_to_pending`::
+ Append unique non-commit objects to the `pending` object list in the
+ passed `rev_info` instance.
+
+`add_names`::
+ Include non-commit object names in the pending object entries if
+ `add_to_pending` is set.
+
+`ignore_size`::
+ If non-zero, ignore slices with size greater or equal to this during
+fusion.
+
+`maps`/`last_map`::
+ An array of slice mappings, indexed by their id in the slice index
+ header, to be re-used with `fuse_me`. `last_map` points to the last
+ mapping used, and should be initialized to 0.
+
+Functions
+~~~~~~~~~
+
+init_rev_cache
+^^^^^^^^^^^^^^
+----
+void init_rev_cache_info(
+ struct rev_cache_info *rci OUT
+)
+----
+
+Initialize `rci` to default options.
+
+make_cache_slice
+^^^^^^^^^^^^^^^^
+----
+int make_cache_slice(
+ struct rev_cache_info *rci IN,
+ struct rev_info *revs IN,
+ struct commit_list **starts IN/OUT,
+ struct commit_list **ends IN/OUT,
+ unsigned char *cache_sha1 OUT
+)
+----
+
+Create a cache slice based on either `revs` (if non-NULL) *or* the `starts` and
+`ends` lists. The actual list of start and end commits of the slice may be
+different from the parameters, based on what defines the branch segment, and
+this actual list is passed back through `starts` and `ends`.
+
+The cache slice is identified via a SHA-1 generated from the actual start/end
+commit lists. `cache_sha1`, if non-NULL, can recieve the cache slice name.
+`rci` is used to specify generation options, but can be NULL if you want
+`make_cache_slice` to fall back on defaults. Returns 0 on success, non-zero on
+failure.
+
+make_cache_index
+^^^^^^^^^^^^^^^^
+----
+int make_cache_index(
+ struct rev_cache_info *rci IN,
+ unsigned char *cache_sha1 IN,
+ int fd IN,
+ unsigned int size IN
+)
+----
+
+Add a slice to the rev-cache index. `cache_sha1` is the identity hash of the
+cache slice; `fd` is a file descriptor of the cache slice opened with
+read/write privileges (the slice is not actually modified); `size` is the size
+of the cache slice. Although there are currently no options for index
+updating, `rci` is a placeholder in case of future options. Note that this
+function is normally called by `make_cache_slice`. Returns 0 on success,
+non-zero on failure.
+
+open_cache_slice
+^^^^^^^^^^^^^^^^
+----
+int open_cache_slice(
+ unsigned char *sha1 IN,
+ int flags IN
+)
+----
+
+Returns a file descriptor to a cache slice described by `sha1` hash, using
+`flags` as the access mode. This will follow cache slice pointers to one level
+of indirection.
+
+get_cache_slice
+^^^^^^^^^^^^^^^
+----
+unsigned char *get_cache_slice(
+ struct commit *commit IN
+)
+----
+
+Given a commit object `get_cache_slice` will search the revision cache index
+and return, if found, the cache slice SHA-1.
+
+traverse_cache_slice
+^^^^^^^^^^^^^^^^^^^^
+----
+int traverse_cache_slice(
+ struct rev_info *revs IN/OUT,
+ unsigned char *cache_sha1 IN,
+ struct commit *commit IN,
+ unsigned long *date_so_far IN/OUT,
+ int *slop_so_far IN/OUT,
+ struct commit_list ***queue OUT,
+ struct commit_list **work IN/OUT
+)
+----
+
+Traverse a specified cache slice. An explanation of the each field:
+
+`revs`::
+ The revision walk instance. `traverse_cache_slice` uses this for
+ general options (e.g. which objects are included) and slice traversal
+ options (in the `rev_cache_info` field). If the `add_to_pending`
+ option is specified, non-commit objects are appended to the `pending`
+ object list field.
+
+`cache_sha1`::
+ SHA-1 identifying the cache slice to use. This can be taken directly
+ from `get_cache_slice`.
+
+`commit`::
+ The current commit object in the revision walk, i.e. the commit which
+ inspired this slice traversal. Although theoretically redundant in
+ view of the `work` list, this simplifies interaction with normal
+ revision walks, which pop commits from `work` before analyzing them.
+
+`date_so_far`::
+ The date of the oldest encountered interesting commit. Passing NULL
+ will let `traverse_cache_slice` use defaults.
+
+`slop_so_far`::
+ The `slop` value, a la revision.c. This is a counter used to determine
+ when to stop traversing, based on how many extra uninteresting commits
+ should be encountered. NULL will enable defaults, as above.
+
+`queue`::
+ Refers to a pointer to the head of a FIFO commit list, recieving the
+ commits we've seen and added.
+
+`work`::
+ A date-ordered list of commits that have yet to be processed (i.e. seen
+ but not added). Commits from here present in the slice are removed
+ (and, obviously, used as starting places for traversal), and any end
+ commits encountered are inserted.
+
+starts_from_slices
+^^^^^^^^^^^^^^^^^^
+----
+void starts_from_slices(
+ struct rev_info *revs OUT,
+ unsigned int flags IN,
+ unsigned char *which IN,
+ int n IN
+)
+----
+
+Will mark start-commits in certain rev-cache slices with `flag`, and added them
+to the pending list of `revs`. If `n` is zero, `starts_from_slices` will use
+all slices. Otherwise `which` will specify an *unseperated* list of cache
+SHA-1s to use (20 bytes each), and `n` will contain the number of slices (i.e.
+20 * `n` = size of `which`).
+
+fuse_cache_slices
+^^^^^^^^^^^^^^^^^
+----
+int fuse_cache_slices(
+ struct rev_cache_info *rci IN,
+ struct rev_info *revs IN
+)
+----
+
+Generate a slice based on `revs`, replacing all encountered slices with one
+(larger) slice. The `ignore_size` field in `rci`, if non-zero, will dictate
+which cache slice sizes to ignore in both traversal and replacement.
+
+regenerate_cache_index
+^^^^^^^^^^^^^^^^^^^^^^
+----
+int regenerate_cache_index(
+ struct rev_cache_info *rci IN
+)
+----
+
+Remake the revision cache index, including all the slices. Currently no
+options in `rci` exist for index (re)generation, but some may develop in the
+future.
+
+to/from_disked_rc_object/index_entry
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+----
+struct rc_object/index_entry *from_disked_rc_object/index_entry(
+ struct rc_object/index_entry_ondisk *src IN,
+ struct rc_object/index_entry *dst OUT
+)
+
+struct rc_object/index_entry_ondisk *to_disked_rc_object/index_entry(
+ struct rc_object/index_entry *src IN,
+ struct rc_object/index_entry_ondisk *dst OUT
+)
+----
+
+Functions to convert between the internal and storage (`_ondisk`) versions of
+object and index entry structures. These are necessary for direct access to
+the cache slices. If NULL is provided for `dst` a statically allocated
+structure is used, and a pointer to the struct is returned. Otherwise the
+functions return `dst`.
+
+Example Usage
+-------------
+
+A few examples to demonstrate usage:
+
+.Creating a slice
+----
+/* pretend you're a porcelain for rev-cache reading from the command line */
+struct rev_info revs;
+struct rev_cache_info rci;
+
+init_revisions(&revs, 0);
+init_rci(&rci);
+
+flags = 0;
+for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "--not"))
+ flags ^= UNINTERESTING;
+ else if(!strcmp(argv[i], "--fresh"))
+ starts_from_slices(&revs, UNINTERESTING, 0, 0);
+ else
+ handle_revision_arg(argv[i], &revs, flags, 1);
+}
+
+/* we want to explicitly set certain options */
+rci.objects = 0;
+
+if (!make_cache_slice(&rci, &revs, 0, 0, cache_sha1))
+ printf("made slice! it's called %s\n", sha1_to_hex(cache_sha1));
+----
+
+.Traversing a slice
+----
+/* let's say you're walking the tree with a 'work' list of current heads and a
+ * FILO output list 'out' */
+out = 0;
+outp = &out;
+
+while (work) {
+ struct commit *commit = pop_commit(&work);
+ struct object *object = &commit->object;
+ unsigned char *cache_sha1;
+
+ if (cache_sha1 = get_cache_slice(object->sha1)) {
+ /* note that this will instatiate any topo-relations
+ * as it goes */
+ if (traverse_cache_slice(&revs, cache_sha1,
+ commit, 0, 0, /* use defaults */
+ &outp, &work) < 0)
+ die("I'm overreacting to a non-fatal cache error");
+ } else {
+ struct commit_list *parents = commit->parents;
+
+ while (parents) {
+ struct commit *p = parents->item;
+ struct object *po = &p->object;
+
+ parents = parents->next;
+ if (po->flags & UNINTERESTING)
+ continue;
+
+ if (object->flags & UNINTERESTING)
+ po->flags |= UNINTERESTING;
+ else if (po->flags & SEEN)
+ continue;
+
+ if (!po->parsed)
+ parse_commit(p);
+ insert_by_date(p, &work);
+ }
+
+ if (object->flags & (SEEN | UNINTERESTING) == 0)
+ outp = &commit_list_insert(commit, outp)->next;
+ object->flags |= SEEN;
+ }
+}
+----
+
+Some Internals
+--------------
+For more advanced usage, the slice and index file(s) may be accessed directly.
+Relavant structures are availabe in `rev-cache.h`.
+
+File Formats
+~~~~~~~~~~~~
+
+Cache Slices
+^^^^^^^^^^^^
+A slice has a basic fixed-size header, followed by a certain number of object
+entries, then a NULL-seperated list of object names. Commits are sorted in
+topo-order, and each commit entry is followed by the objects added in that
+commit.
+
+----
+ -- +--------------------------------+
+header | object number, etc... |
+ -- +--------------------------------+
+commit | commit info |
+entry | path data |
+ +--------------------------------+
+ | tree/blob info |
+ +--------------------------------+
+ | tree/blob info |
+ +--------------------------------+
+ | ... |
+ -- +--------------------------------+
+commit | commit info |
+entry | path data |
+ +--------------------------------+
+ | tree/blob info |
+ +--------------------------------+
+ | ... |
+ -- +--------------------------------+
+... ...
+ -- +--------------------------------+
+name list | \0some_file_name\0 |
+(note +--------------------------------+
+preceeding | another_file\0 |
+null) ... |
+ +--------------------------------+
+----
+
+Here is the header:
+
+----
+struct rc_cache_slice_header {
+ char signature[8]; /* REVCACHE */
+ unsigned char version;
+ uint32_t ofs_objects;
+
+ uint32_t object_nr;
+ uint16_t path_nr;
+ uint32_t size;
+
+ unsigned char sha1[20];
+
+ uint32_t names_size;
+};
+----
+
+Explanations:
+
+`signature`::
+ The identifying signature of cache slice file. Always "REVCACHE".
+`version`::
+ The version number, currently 1.
+`ofs_objects`::
+ The byte offset at which the commit/object listing starts. Always
+ present at the 10th byte, regardless of file version.
+`object_nr`::
+ The total number of objects (commit + non-commit objects) present in
+ the slice.
+`path_nr`::
+ The total number of paths/channels used in encoding the topological
+ data. Note that paths are reused (see 'Mechanics'), so there will
+ never be more than a few hundred paths (if that) used.
+`size`::
+ The size of the slice *excluding* the name list. In other words, the
+ size of the portion mapped to memory.
+`sha1`::
+ The cache slice SHA-1.
+`names_size`::
+ The size of the name list. `size` + `names_size` = size of slice
+
+Revision Cache Index
+^^^^^^^^^^^^^^^^^^^^
+The index is a single file that associates SHA-1s with cache slices and file
+positions. It is somewhat similar to pack-file indexes, containing a fanout
+table and a list of index entries sorted by hash.
+
+----
+ -- +--------------------------------+
+header | object #, cache #, etc. |
+ -- +--------------------------------+
+sha1s of | SHA-1 |
+slices | ... |
+ -- +--------------------------------+
+fanout | fanout[0x00] |
+table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ | fanout[0xff] |
+ -- +--------------------------------+
+index | SHA-1 of object |
+entries | index of cache slice SHA-1 |
+ | position in cache slice |
+ +--------------------------------+
+ | |
+ ...
+ +--------------------------------+
+----
+
+The header:
+
+----
+struct rc_index_header {
+ char signature[8]; /* REVINDEX */
+ unsigned char version;
+ uint32_t ofs_objects;
+
+ uint32_t object_nr;
+ unsigned char cache_nr;
+
+ uint32_t max_date;
+};
+----
+
+Explanations:
+
+`signature`::
+ Always "REVINDEX".
+`version`::
+ Version number, currently 1.
+`ofs_objects`::
+ Offset at which the entry objects begin. This is more obviously useful
+ in the index because the list of slice SHA-1s is variably-sized.
+`object_nr`::
+ Number of index entry objects present.
+`cache_nr`::
+ Number of cache slices to which the index maps, and hence the number of
+slice SHA-1s listed.
+`max_date`::
+ The oldest commit represented in the index. This is used to help speed
+up lookup times by knowing what range of commits we definitely don't have
+cached. Normal usage of 'rev-cache' would leave no "holes" in its coverage of
+commit history -- once a commit is cached, everything reachable from it should
+be cached as well. Most of the time refs are added to rev-cache simultaneous
+as well. This means that in most situations almost everything <= `max_date`
+will be cached.
+
+Mechanics
+~~~~~~~~~
+
+The most important part of rev-cache is its method of encoding topological
+relations. To ensure fluid traversal and reconstruction, commits are related
+through high-level "streams"/"channels" rather than individual
+interconnections. Intuitively, rev-cache stores history the way gitk shows it:
+commits strung up on lines, which interconnect at merges and branches.
+
+Each commit is associated to a given channel/path via a 'path id', and
+variable-length fields govern which paths (if any) are closed or opened at that
+object. This means that topo-data can be preserved in only a few bytes extra
+per object entry. Other information stored per entry is the sha-1 hash, type,
+date, size, name, and status in cache slice. Here is format of an object
+entry, both on-disk and in-memory:
+
+----
+struct object_entry {
+ unsigned type : 3;
+ unsigned is_end : 1;
+ unsigned is_start : 1;
+ unsigned uninteresting : 1;
+ unsigned include : 1;
+ unsigned flags : 1;
+ unsigned char sha1[20];
+
+ unsigned char merge_nr;
+ unsigned char split_nr;
+ unsigned size_size : 3;
+ unsigned name_size : 3;
+
+ uint32_t date;
+ uint16_t path;
+
+ /* merge paths */
+ /* split paths */
+ /* size */
+ /* name index */
+};
+----
+
+An explanation of each field:
+
+`type`::
+ Object type
+`is_end`::
+ The commit has some parents outside the cache slice (all if slice has
+ legs)
+`is_start`::
+ The commit has no children in cache slice
+`uninteresting`::
+ Run-time flag, used in traversal
+`include`::
+ Run-time flag, used in traversal (initialization)
+`flags`::
+ Currently unused, extra bit
+`sha1`::
+ Object SHA-1 hash
+
+`merge_nr`::
+ The number of paths the current channel diverges into; the current path
+ ends upon any merge.
+`split_nr`::
+ The number of paths this commit ends; used on both merging and
+ branching.
+`size_size`::
+ Number of bytes the object size takes up.
+`name_size`::
+ Number of bytes the name index takes up.
+
+`date`::
+ The date of the commit.
+`path`::
+ The path ID of the channel with which this commit is associated.
+
+merge paths::
+ The path IDs (16-bit) that are to be created. Overflow is not a
+ problem as path IDs are reused, leaving even complicated projects to
+ consume no more than a few hundred IDs.
+split paths::
+ The path IDs (16-bit) that are to be ended.
+size::
+ The size split into the minimum number of bytes. That is, 1-8 bytes
+ representing the size, least-significant byte first.
+name index::
+ An offset for the null-seperated, object name list at the end of the
+ cache slice. Also split into the minimum number of bytes.
+
+Each path ID refers to an index in a 'path array', which stores the current
+status (eg. active, interestingness) of each channel.
+
+Due to topo-relations and boundary tracking, all of a commit's parents must be
+encountered before the path is reallocated. This is achieved by using a
+counter system per merge: starting at the parent number, the counter is
+decremented as each parent is encountered (dictated by 'split paths'); at 0 the
+path is cleared.
+
+Boundary tracking is necessary because non-commits are stored relative to the
+commit in which they were introduced. If a series of commits is not included
+in the output, the last interesting commit must be parsed manually to ensure
+all objects are accounted for.
+
+To prevent list-objects from recursing into trees that we've already taken care
+of, the flag `FACE_VALUE` is introduced. An object with this flag is not
+explored (= "taken at face value"), significantly reducing I/O and processing
+time.
--
tg: (797cc19..) t/revcache/docs (depends on: t/revcache/integration)
^ permalink raw reply related
* Re: [PATCH 3/6 (v2)] non-commit object support for rev-cache
From: Nick Edelen @ 2009-08-16 22:41 UTC (permalink / raw)
To: Nick Edelen, Junio C Hamano, Nicolas Pitre, Johannes Schindelin,
Sam Vilain
In-Reply-To: <op.uyb1u5pjtdk399@sirnot.private>
Summarized, this third patch contains:
- support for non-commit object caching
- expansion of porcelain to accomodate non-commit objects
- appropriate tests
Objects are stored relative to the commit in which they were introduced --
commits are 'diffed' against their parents. This will eliminate the need for
tree recursion in cached commits (significantly reducing I/O), and potentially
be useful to external applications.
Signed-off-by: Nick Edelen <sirnot@gmail.com>
---
rev-cache.c | 204 ++++++++++++++++++++++++++++++++++++++++++++-
t/t6015-rev-cache-list.sh | 8 ++
2 files changed, 209 insertions(+), 3 deletions(-)
diff --git a/rev-cache.c b/rev-cache.c
index a01db87..ddcc596 100644
--- a/rev-cache.c
+++ b/rev-cache.c
@@ -257,6 +257,32 @@ unsigned char *get_cache_slice(struct commit *commit)
/* traversal */
+static void handle_noncommit(struct rev_info *revs, unsigned char *ptr, struct rc_object_entry *entry)
+{
+ struct object *obj = 0;
+
+ switch (entry->type) {
+ case OBJ_TREE :
+ if (revs->tree_objects)
+ obj = (struct object *)lookup_tree(entry->sha1);
+ break;
+ case OBJ_BLOB :
+ if (revs->blob_objects)
+ obj = (struct object *)lookup_blob(entry->sha1);
+ break;
+ case OBJ_TAG :
+ if (revs->tag_objects)
+ obj = (struct object *)lookup_tag(entry->sha1);
+ break;
+ }
+
+ if (!obj)
+ return;
+
+ obj->flags |= FACE_VALUE;
+ add_pending_object(revs, obj, "");
+}
+
static int setup_traversal(struct rc_slice_header *head, unsigned char *map, struct commit *commit, struct commit_list **work)
{
struct rc_index_entry *iep;
@@ -293,7 +319,7 @@ static int setup_traversal(struct rc_slice_header *head, unsigned char *map, str
if (iep->pos < retval)
retval = iep->pos;
-
+
oep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);
/* mark this for later */
@@ -345,9 +371,12 @@ static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *m
i += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);
/* add extra objects if necessary */
- if (entry->type != OBJ_COMMIT)
+ if (entry->type != OBJ_COMMIT) {
+ if (consume_children)
+ handle_noncommit(revs, map + index, entry);
+
continue;
- else
+ } else
consume_children = 0;
if (path >= total_path_nr)
@@ -775,6 +804,171 @@ static void add_object_entry(const unsigned char *sha1, int type, struct rc_obje
}
+/* returns non-zero to continue parsing, 0 to skip */
+typedef int (*dump_tree_fn)(const unsigned char *, const char *, unsigned int); /* sha1, path, mode */
+
+/* we need to walk the trees by hash, so unfortunately we can't use traverse_trees in tree-walk.c */
+static int dump_tree(struct tree *tree, dump_tree_fn fn)
+{
+ struct tree_desc desc;
+ struct name_entry entry;
+ struct tree *subtree;
+ int r;
+
+ if (parse_tree(tree))
+ return -1;
+
+ init_tree_desc(&desc, tree->buffer, tree->size);
+ while (tree_entry(&desc, &entry)) {
+ switch (fn(entry.sha1, entry.path, entry.mode)) {
+ case 0 :
+ goto continue_loop;
+ default :
+ break;
+ }
+
+ if (S_ISDIR(entry.mode)) {
+ subtree = lookup_tree(entry.sha1);
+ if (!subtree)
+ return -2;
+
+ if ((r = dump_tree(subtree, fn)) < 0)
+ return r;
+ }
+
+continue_loop:
+ continue;
+ }
+
+ return 0;
+}
+
+static int dump_tree_callback(const unsigned char *sha1, const char *path, unsigned int mode)
+{
+ unsigned char data[21];
+
+ hashcpy(data, sha1);
+ data[20] = !!S_ISDIR(mode);
+
+ strbuf_add(acc_buffer, data, 21);
+
+ return 1;
+}
+
+static void tree_addremove(struct diff_options *options,
+ int whatnow, unsigned mode,
+ const unsigned char *sha1,
+ const char *concatpath)
+{
+ unsigned char data[21];
+
+ if (whatnow != '+')
+ return;
+
+ hashcpy(data, sha1);
+ data[20] = !!S_ISDIR(mode);
+
+ strbuf_add(acc_buffer, data, 21);
+}
+
+static void tree_change(struct diff_options *options,
+ unsigned old_mode, unsigned new_mode,
+ const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *concatpath)
+{
+ unsigned char data[21];
+
+ if (!hashcmp(old_sha1, new_sha1))
+ return;
+
+ hashcpy(data, new_sha1);
+ data[20] = !!S_ISDIR(new_mode);
+
+ strbuf_add(acc_buffer, data, 21);
+}
+
+static int sort_type_hash(const void *a, const void *b)
+{
+ const unsigned char *sa = (const unsigned char *)a,
+ *sb = (const unsigned char *)b;
+
+ if (sa[20] == sb[20])
+ return hashcmp(sa, sb);
+
+ return sa[20] > sb[20] ? -1 : 1;
+}
+
+static int add_unique_objects(struct commit *commit)
+{
+ struct commit_list *list;
+ struct strbuf os, ost, *orig_buf;
+ struct diff_options opts;
+ int i, j, next;
+ char is_first = 1;
+
+ strbuf_init(&os, 0);
+ strbuf_init(&ost, 0);
+ orig_buf = acc_buffer;
+
+ diff_setup(&opts);
+ DIFF_OPT_SET(&opts, RECURSIVE);
+ DIFF_OPT_SET(&opts, TREE_IN_RECURSIVE);
+ opts.change = tree_change;
+ opts.add_remove = tree_addremove;
+
+ /* this is only called for non-ends (ie. all parents interesting) */
+ for (list = commit->parents; list; list = list->next) {
+ if (is_first)
+ acc_buffer = &os;
+ else
+ acc_buffer = &ost;
+
+ strbuf_setlen(acc_buffer, 0);
+ diff_tree_sha1(list->item->tree->object.sha1, commit->tree->object.sha1, "", &opts);
+ qsort(acc_buffer->buf, acc_buffer->len / 21, 21, (int (*)(const void *, const void *))hashcmp);
+
+ /* take intersection */
+ if (!is_first) {
+ for (next = i = j = 0; i < os.len; i += 21) {
+ while (j < ost.len && hashcmp((unsigned char *)(ost.buf + j), (unsigned char *)(os.buf + i)) < 0)
+ j += 21;
+
+ if (j >= ost.len || hashcmp((unsigned char *)(ost.buf + j), (unsigned char *)(os.buf + i)))
+ continue;
+
+ if (next != i)
+ memcpy(os.buf + next, os.buf + i, 21);
+ next += 21;
+ }
+
+ if (next != i)
+ strbuf_setlen(&os, next);
+ } else
+ is_first = 0;
+ }
+
+ if (is_first) {
+ acc_buffer = &os;
+ dump_tree(commit->tree, dump_tree_callback);
+ }
+
+ if (os.len)
+ qsort(os.buf, os.len / 21, 21, sort_type_hash);
+
+ acc_buffer = orig_buf;
+ for (i = 0; i < os.len; i += 21)
+ add_object_entry((unsigned char *)(os.buf + i), os.buf[i + 20] ? OBJ_TREE : OBJ_BLOB, 0, 0, 0);
+
+ /* last but not least, the main tree */
+ add_object_entry(commit->tree->object.sha1, OBJ_TREE, 0, 0, 0);
+
+ strbuf_release(&ost);
+ strbuf_release(&os);
+
+ return i / 21 + 1;
+}
+
static void init_revcache_directory(void)
{
struct stat fi;
@@ -900,6 +1094,10 @@ int make_cache_slice(struct rev_cache_info *rci,
add_object_entry(0, 0, &object, &merge_paths, &split_paths);
object_nr++;
+ /* add all unique children for this commit */
+ if (rci->objects && !object.is_end)
+ object_nr += add_unique_objects(commit);
+
/* print every ~1MB or so */
if (buffer.len > 1000000) {
write_in_full(fd, buffer.buf, buffer.len);
diff --git a/t/t6015-rev-cache-list.sh b/t/t6015-rev-cache-list.sh
index e7474fd..afa0303 100755
--- a/t/t6015-rev-cache-list.sh
+++ b/t/t6015-rev-cache-list.sh
@@ -79,6 +79,7 @@ test_expect_success 'init repo' '
git-rev-list HEAD --not HEAD~3 >proper_commit_list_limited
git-rev-list HEAD >proper_commit_list
+git-rev-list HEAD --objects >proper_object_list
test_expect_success 'make cache slice' '
git-rev-cache add HEAD 2>output.err &&
@@ -101,4 +102,11 @@ test_expect_success 'test rev-caches walker directly (unlimited)' '
test_cmp_sorted list proper_commit_list
'
+#do the same for objects
+test_expect_success 'test rev-caches walker with objects' '
+ git-rev-cache walk --objects HEAD >list &&
+ test_cmp_sorted list proper_object_list
+'
+
test_done
+
--
tg: (cbb441e..) t/revcache/objects (depends on: t/revcache/basic)
^ permalink raw reply related
* Re: [PATCH 2/6 (v2)] bare minimum revision cache system, no integration with git
From: Nick Edelen @ 2009-08-16 22:41 UTC (permalink / raw)
To: Nick Edelen, Junio C Hamano, Nicolas Pitre, Johannes Schindelin,
Sam Vilain
In-Reply-To: <op.uyb1uz10tdk399@sirnot.private>
Second in the revision cache series, this particular patch provides:
- minimal API: caching only commit topo data
- minimal porcelain: add and walk cache slices
- appropriate tests
Signed-off-by: Nick Edelen <sirnot@gmail.com>
---
Makefile | 2 +
builtin-rev-cache.c | 206 ++++++++
builtin.h | 1 +
commit.c | 2 +
git.c | 1 +
rev-cache.c | 1169 +++++++++++++++++++++++++++++++++++++++++++++
rev-cache.h | 107 ++++
revision.c | 2 +-
revision.h | 26 +-
t/t6015-rev-cache-list.sh | 104 ++++
10 files changed, 1618 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index daf4296..386700a 100644
--- a/Makefile
+++ b/Makefile
@@ -533,6 +533,7 @@ LIB_OBJS += reflog-walk.o
LIB_OBJS += refs.o
LIB_OBJS += remote.o
LIB_OBJS += rerere.o
+LIB_OBJS += rev-cache.o
LIB_OBJS += revision.o
LIB_OBJS += run-command.o
LIB_OBJS += server-info.o
@@ -623,6 +624,7 @@ BUILTIN_OBJS += builtin-reflog.o
BUILTIN_OBJS += builtin-remote.o
BUILTIN_OBJS += builtin-rerere.o
BUILTIN_OBJS += builtin-reset.o
+BUILTIN_OBJS += builtin-rev-cache.o
BUILTIN_OBJS += builtin-rev-list.o
BUILTIN_OBJS += builtin-rev-parse.o
BUILTIN_OBJS += builtin-revert.o
diff --git a/builtin-rev-cache.c b/builtin-rev-cache.c
new file mode 100644
index 0000000..c27e0f3
--- /dev/null
+++ b/builtin-rev-cache.c
@@ -0,0 +1,206 @@
+#include "cache.h"
+#include "object.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "rev-cache.h"
+
+/* porcelain for rev-cache.c */
+static int handle_add(int argc, const char *argv[]) /* args beyond this command */
+{
+ struct rev_info revs;
+ struct rev_cache_info rci;
+ char dostdin = 0;
+ unsigned int flags = 0;
+ int i, retval;
+ unsigned char cache_sha1[20];
+ struct commit_list *starts = 0, *ends = 0;
+ struct commit *commit;
+
+ init_revisions(&revs, 0);
+ init_rev_cache_info(&rci);
+
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "--stdin"))
+ dostdin = 1;
+ else if (!strcmp(argv[i], "--fresh"))
+ starts_from_slices(&revs, UNINTERESTING);
+ else if (!strcmp(argv[i], "--not"))
+ flags ^= UNINTERESTING;
+ else if (!strcmp(argv[i], "--legs"))
+ rci.legs = 1;
+ else if (!strcmp(argv[i], "--no-objects"))
+ rci.objects = 0;
+ else if (!strcmp(argv[i], "--all")) {
+ const char *args[2];
+ int argn = 0;
+
+ args[argn++] = "rev-list";
+ args[argn++] = "--all";
+ setup_revisions(argn, args, &revs, 0);
+ } else
+ handle_revision_arg(argv[i], &revs, flags, 1);
+ }
+
+ if (dostdin) {
+ char line[1000];
+
+ flags = 0;
+ while (fgets(line, sizeof(line), stdin)) {
+ int len = strlen(line);
+ while (len && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+ line[--len] = 0;
+
+ if (!len)
+ break;
+
+ if (!strcmp(line, "--not"))
+ flags ^= UNINTERESTING;
+ else
+ handle_revision_arg(line, &revs, flags, 1);
+ }
+ }
+
+ retval = make_cache_slice(&rci, &revs, &starts, &ends, cache_sha1);
+ if (retval < 0)
+ return retval;
+
+ printf("%s\n", sha1_to_hex(cache_sha1));
+
+ fprintf(stderr, "endpoints:\n");
+ while ((commit = pop_commit(&starts)))
+ fprintf(stderr, "S %s\n", sha1_to_hex(commit->object.sha1));
+ while ((commit = pop_commit(&ends)))
+ fprintf(stderr, "E %s\n", sha1_to_hex(commit->object.sha1));
+
+ return 0;
+}
+
+static int handle_walk(int argc, const char *argv[])
+{
+ struct commit *commit;
+ struct rev_info revs;
+ struct commit_list *queue, *work, **qp;
+ unsigned char *sha1p, *sha1pt;
+ unsigned long date = 0;
+ unsigned int flags = 0;
+ int retval, slop = 5, i;
+
+ init_revisions(&revs, 0);
+
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "--not"))
+ flags ^= UNINTERESTING;
+ else if (!strcmp(argv[i], "--objects"))
+ revs.tree_objects = revs.blob_objects = 1;
+ else
+ handle_revision_arg(argv[i], &revs, flags, 1);
+ }
+
+ work = 0;
+ sha1p = 0;
+ for (i = 0; i < revs.pending.nr; i++) {
+ commit = lookup_commit(revs.pending.objects[i].item->sha1);
+
+ sha1pt = get_cache_slice(commit);
+ if (!sha1pt)
+ die("%s: not in a cache slice", sha1_to_hex(commit->object.sha1));
+
+ if (!i)
+ sha1p = sha1pt;
+ else if (sha1p != sha1pt)
+ die("walking porcelain is /per/ cache slice; commits cannot be spread out amoung several");
+
+ insert_by_date(commit, &work);
+ }
+
+ if (!sha1p)
+ die("nothing to traverse!");
+
+ queue = 0;
+ qp = &queue;
+ commit = pop_commit(&work);
+ retval = traverse_cache_slice(&revs, sha1p, commit, &date, &slop, &qp, &work);
+ if (retval < 0)
+ return retval;
+
+ fprintf(stderr, "queue:\n");
+ while ((commit = pop_commit(&queue)) != 0) {
+ printf("%s\n", sha1_to_hex(commit->object.sha1));
+ }
+
+ fprintf(stderr, "work:\n");
+ while ((commit = pop_commit(&work)) != 0) {
+ printf("%s\n", sha1_to_hex(commit->object.sha1));
+ }
+
+ fprintf(stderr, "pending:\n");
+ for (i = 0; i < revs.pending.nr; i++) {
+ struct object *obj = revs.pending.objects[i].item;
+
+ /* unfortunately, despite our careful generation, object duplication *is* a possibility...
+ * (eg. same object introduced into two different branches) */
+ if (obj->flags & SEEN)
+ continue;
+
+ printf("%s\n", sha1_to_hex(revs.pending.objects[i].item->sha1));
+ obj->flags |= SEEN;
+ }
+
+ return 0;
+}
+
+static int handle_help(void)
+{
+ char *usage = "\
+usage:\n\
+git-rev-cache COMMAND [options] [<commit-id>...]\n\
+commands:\n\
+ add - add revisions to the cache. reads commit ids from stdin, \n\
+ formatted as: START START ... --not END END ...\n\
+ options:\n\
+ --all use all branch heads as starts\n\
+ --fresh exclude everything already in a cache slice\n\
+ --stdin also read commit ids from stdin (same form as cmd)\n\
+ --legs ensure branch is entirely self-contained\n\
+ --no-objects don't add non-commit objects to slice\n\
+ walk - walk a cache slice based on set of commits; formatted as add\n\
+ options:\n\
+ --objects include non-commit objects in traversals\n\
+ fuse - coalesce cache slices into a single cache.\n\
+ options:\n\
+ --all include all objects in repository\n\
+ --no-objects don't add non-commit objects to slice\n\
+ --ignore-size[=N] ignore slices of size >= N; defaults to ~5MB\n\
+ index - regnerate the cache index.";
+
+ puts(usage);
+
+ return 0;
+}
+
+int cmd_rev_cache(int argc, const char *argv[], const char *prefix)
+{
+ const char *arg;
+ int r;
+
+ git_config(git_default_config, NULL);
+
+ if (argc > 1)
+ arg = argv[1];
+ else
+ arg = "";
+
+ argc -= 2;
+ argv += 2;
+ if (!strcmp(arg, "add"))
+ r = handle_add(argc, argv);
+ else if (!strcmp(arg, "walk"))
+ r = handle_walk(argc, argv);
+ else
+ return handle_help();
+
+ fprintf(stderr, "final return value: %d\n", r);
+
+ return 0;
+}
diff --git a/builtin.h b/builtin.h
index 20427d2..00ecc9c 100644
--- a/builtin.h
+++ b/builtin.h
@@ -87,6 +87,7 @@ extern int cmd_remote(int argc, const char **argv, const char *prefix);
extern int cmd_config(int argc, const char **argv, const char *prefix);
extern int cmd_rerere(int argc, const char **argv, const char *prefix);
extern int cmd_reset(int argc, const char **argv, const char *prefix);
+extern int cmd_rev_cache(int argc, const char **argv, const char *prefix);
extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
extern int cmd_revert(int argc, const char **argv, const char *prefix);
diff --git a/commit.c b/commit.c
index e2bcbe8..682e7a7 100644
--- a/commit.c
+++ b/commit.c
@@ -252,6 +252,8 @@ int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
item->tree = lookup_tree(parent);
bufptr += 46; /* "tree " + "hex sha1" + "\n" */
pptr = &item->parents;
+ while (pop_commit(pptr))
+ ; /* clear anything from cache */
graft = lookup_commit_graft(item->object.sha1);
while (bufptr + 48 < tail && !memcmp(bufptr, "parent ", 7)) {
diff --git a/git.c b/git.c
index 4588a8b..7105c74 100644
--- a/git.c
+++ b/git.c
@@ -342,6 +342,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "repo-config", cmd_config },
{ "rerere", cmd_rerere, RUN_SETUP },
{ "reset", cmd_reset, RUN_SETUP },
+ { "rev-cache", cmd_rev_cache, RUN_SETUP },
{ "rev-list", cmd_rev_list, RUN_SETUP },
{ "rev-parse", cmd_rev_parse },
{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
diff --git a/rev-cache.c b/rev-cache.c
new file mode 100644
index 0000000..a01db87
--- /dev/null
+++ b/rev-cache.c
@@ -0,0 +1,1169 @@
+#include "cache.h"
+#include "object.h"
+#include "commit.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "blob.h"
+#include "tag.h"
+#include "diff.h"
+#include "revision.h"
+#include "rev-cache.h"
+#include "run-command.h"
+
+/* list resembles pack index format */
+static uint32_t fanout[0xff + 2];
+
+static unsigned char *idx_map;
+static int idx_size;
+static struct rc_index_header idx_head;
+static unsigned char *idx_caches;
+static char no_idx;
+
+static struct strbuf *acc_buffer;
+
+#define SLOP 5
+
+/* initialization */
+
+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst)
+{
+ static struct rc_index_entry entry[4];
+ static int cur;
+
+ if (!dst)
+ dst = &entry[cur++ & 0x3];
+
+ dst->sha1 = src->sha1;
+ dst->is_start = !!(src->flags & 0x80);
+ dst->cache_index = src->flags & 0x7f;
+ dst->pos = ntohl(src->pos);
+
+ return dst;
+}
+
+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst)
+{
+ static struct rc_index_entry_ondisk entry[4];
+ static int cur;
+
+ if (!dst)
+ dst = &entry[cur++ & 0x3];
+
+ hashcpy(dst->sha1, src->sha1);
+ dst->flags = (unsigned char)src->is_start << 7 | (unsigned char)src->cache_index;
+ dst->pos = htonl(src->pos);
+
+ return dst;
+}
+
+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst)
+{
+ static struct rc_object_entry entry[4];
+ static int cur;
+
+ if (!dst)
+ dst = &entry[cur++ & 0x3];
+
+ dst->type = src->flags >> 5;
+ dst->is_end = !!(src->flags & 0x10);
+ dst->is_start = !!(src->flags & 0x08);
+ dst->uninteresting = !!(src->flags & 0x04);
+ dst->include = !!(src->flags & 0x02);
+ dst->flag = !!(src->flags & 0x01);
+
+ dst->sha1 = src->sha1;
+ dst->merge_nr = src->merge_nr;
+ dst->split_nr = src->split_nr;
+
+ dst->size_size = src->sizes >> 5;
+ dst->padding = src->sizes & 0x1f;
+
+ dst->date = ntohl(src->date);
+ dst->path = ntohs(src->path);
+
+ return dst;
+}
+
+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst)
+{
+ static struct rc_object_entry_ondisk entry[4];
+ static int cur;
+
+ if (!dst)
+ dst = &entry[cur++ & 0x3];
+
+ dst->flags = (unsigned char)src->type << 5;
+ dst->flags |= (unsigned char)src->is_end << 4;
+ dst->flags |= (unsigned char)src->is_start << 3;
+ dst->flags |= (unsigned char)src->uninteresting << 2;
+ dst->flags |= (unsigned char)src->include << 1;
+ dst->flags |= (unsigned char)src->flag;
+
+ hashcpy(dst->sha1, src->sha1);
+ dst->merge_nr = src->merge_nr;
+ dst->split_nr = src->split_nr;
+
+ dst->sizes = (unsigned char)src->size_size << 5;
+ dst->sizes |= (unsigned char)src->padding;
+
+ dst->date = htonl(src->date);
+ dst->path = htons(src->path);
+
+ return dst;
+}
+
+static int get_index_head(unsigned char *map, int len, struct rc_index_header *head, uint32_t *fanout, unsigned char **caches)
+{
+ struct rc_index_header whead;
+ int i, index = sizeof(struct rc_index_header);
+
+ memcpy(&whead, map, sizeof(struct rc_index_header));
+ if (memcmp(whead.signature, "REVINDEX", 8) || whead.version != SUPPORTED_REVINDEX_VERSION)
+ return -1;
+
+ memcpy(head->signature, "REVINDEX", 8);
+ head->version = whead.version;
+ head->ofs_objects = ntohl(whead.ofs_objects);
+ head->object_nr = ntohl(whead.object_nr);
+ head->cache_nr = whead.cache_nr;
+ head->max_date = ntohl(whead.max_date);
+
+ if (len < index + head->cache_nr * 20 + 0x100 * sizeof(uint32_t))
+ return -2;
+
+ *caches = xmalloc(head->cache_nr * 20);
+ memcpy(*caches, map + index, head->cache_nr * 20);
+ index += head->cache_nr * 20;
+
+ memcpy(fanout, map + index, 0x100 * sizeof(uint32_t));
+ for (i = 0; i <= 0xff; i++)
+ fanout[i] = ntohl(fanout[i]);
+ fanout[0x100] = len;
+
+ return 0;
+}
+
+/* added in init_index */
+static void cleanup_cache_slices(void)
+{
+ if (idx_map) {
+ free(idx_caches);
+ munmap(idx_map, idx_size);
+ idx_map = 0;
+ }
+
+}
+
+static int init_index(void)
+{
+ int fd;
+ struct stat fi;
+
+ fd = open(git_path("rev-cache/index"), O_RDONLY);
+ if (fd == -1 || fstat(fd, &fi))
+ goto end;
+ if (fi.st_size < sizeof(struct rc_index_header))
+ goto end;
+
+ idx_size = fi.st_size;
+ idx_map = xmmap(0, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (idx_map == MAP_FAILED)
+ goto end;
+ if (get_index_head(idx_map, fi.st_size, &idx_head, fanout, &idx_caches))
+ goto end;
+
+ atexit(cleanup_cache_slices);
+
+ return 0;
+
+end:
+ idx_map = 0;
+ no_idx = 1;
+ return -1;
+}
+
+/* this assumes index is already loaded */
+static struct rc_index_entry_ondisk *search_index_1(unsigned char *sha1)
+{
+ int start, end, starti, endi, i, len, r;
+ struct rc_index_entry_ondisk *ie;
+
+ if (!idx_map)
+ return 0;
+
+ /* binary search */
+ start = fanout[(int)sha1[0]];
+ end = fanout[(int)sha1[0] + 1];
+ len = (end - start) / sizeof(struct rc_index_entry_ondisk);
+ if (!len || len * sizeof(struct rc_index_entry_ondisk) != end - start)
+ return 0;
+
+ starti = 0;
+ endi = len - 1;
+ for (;;) {
+ i = (endi + starti) / 2;
+ ie = (struct rc_index_entry_ondisk *)(idx_map + start + i * sizeof(struct rc_index_entry_ondisk));
+ r = hashcmp(sha1, ie->sha1);
+
+ if (r) {
+ if (starti + 1 == endi) {
+ starti++;
+ continue;
+ } else if (starti == endi)
+ break;
+
+ if (r > 0)
+ starti = i;
+ else /* r < 0 */
+ endi = i;
+ } else
+ return ie;
+ }
+
+ return 0;
+}
+
+static struct rc_index_entry *search_index(unsigned char *sha1)
+{
+ struct rc_index_entry_ondisk *ied = search_index_1(sha1);
+
+ if (ied)
+ return from_disked_rc_index_entry(ied, 0);
+
+ return 0;
+}
+
+unsigned char *get_cache_slice(struct commit *commit)
+{
+ struct rc_index_entry *ie;
+
+ if (!idx_map) {
+ if (no_idx)
+ return 0;
+ init_index();
+ }
+
+ if (commit->date > idx_head.max_date)
+ return 0;
+
+ ie = search_index(commit->object.sha1);
+ if (ie && ie->cache_index < idx_head.cache_nr)
+ return idx_caches + ie->cache_index * 20;
+
+ return 0;
+}
+
+
+/* traversal */
+
+static int setup_traversal(struct rc_slice_header *head, unsigned char *map, struct commit *commit, struct commit_list **work)
+{
+ struct rc_index_entry *iep;
+ struct rc_object_entry *oep;
+ struct commit_list *prev, *wp, **wpp;
+ int retval;
+
+ iep = search_index(commit->object.sha1), 0;
+ oep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);
+
+ /* the .uniniteresting bit isn't strictly necessary, as we check the object during traversal as well,
+ * but we might as well initialize it while we're at it */
+ oep->include = 1;
+ oep->uninteresting = !!(commit->object.flags & UNINTERESTING);
+ to_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));
+ retval = iep->pos;
+
+ /* include any others in the work array */
+ prev = 0;
+ wpp = work;
+ wp = *work;
+ while (wp) {
+ struct object *obj = &wp->item->object;
+ struct commit *co;
+
+ /* is this in our cache slice? */
+ iep = search_index(obj->sha1);
+ if (!iep || hashcmp(idx_caches + iep->cache_index * 20, head->sha1)) {
+ prev = wp;
+ wp = wp->next;
+ wpp = ℘
+ continue;
+ }
+
+ if (iep->pos < retval)
+ retval = iep->pos;
+
+ oep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);
+
+ /* mark this for later */
+ oep->include = 1;
+ oep->uninteresting = !!(obj->flags & UNINTERESTING);
+ to_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));
+
+ /* remove from work list */
+ co = pop_commit(wpp);
+ wp = *wpp;
+ if (prev)
+ prev->next = wp;
+ }
+
+ return retval;
+}
+
+#define IPATH 0x40
+#define UPATH 0x80
+
+#define GET_COUNT(x) ((x) & 0x3f)
+#define SET_COUNT(x, s) ((x) = ((x) & ~0x3f) | ((s) & 0x3f))
+
+static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *map,
+ struct rev_info *revs, struct commit *commit,
+ unsigned long *date_so_far, int *slop_so_far,
+ struct commit_list ***queue, struct commit_list **work)
+{
+ struct commit_list *insert_cache = 0;
+ struct commit **last_objects, *co;
+ int i, total_path_nr = head->path_nr, retval = -1;
+ char consume_children = 0;
+ unsigned char *paths;
+
+ i = setup_traversal(head, map, commit, work);
+ if (i < 0)
+ return -1;
+
+ paths = xcalloc(total_path_nr, sizeof(uint16_t));
+ last_objects = xcalloc(total_path_nr, sizeof(struct commit *));
+
+ /* i already set */
+ while (i < head->size) {
+ struct rc_object_entry *entry = RC_OBTAIN_OBJECT_ENTRY(map + i);
+ int path = entry->path;
+ struct object *obj;
+ int index = i;
+
+ i += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);
+
+ /* add extra objects if necessary */
+ if (entry->type != OBJ_COMMIT)
+ continue;
+ else
+ consume_children = 0;
+
+ if (path >= total_path_nr)
+ goto end;
+
+ /* in one of our branches?
+ * uninteresting trumps interesting */
+ if (entry->include)
+ paths[path] |= entry->uninteresting ? UPATH : IPATH;
+ else if (!paths[path])
+ continue;
+
+ /* date stuff */
+ if (revs->max_age != -1 && entry->date < revs->max_age)
+ paths[path] |= UPATH;
+
+ /* lookup object */
+ co = lookup_commit(entry->sha1);
+ obj = &co->object;
+
+ if (obj->flags & UNINTERESTING)
+ paths[path] |= UPATH;
+
+ if ((paths[path] & IPATH) && (paths[path] & UPATH)) {
+ paths[path] = UPATH;
+
+ /* mark edge */
+ if (last_objects[path]) {
+ parse_commit(last_objects[path]);
+
+ last_objects[path]->object.flags &= ~FACE_VALUE;
+ last_objects[path] = 0;
+ }
+ }
+
+ /* now we gotta re-assess the whole interesting thing... */
+ entry->uninteresting = !!(paths[path] & UPATH);
+
+ /* first close paths */
+ if (entry->split_nr) {
+ int j, off = index + sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE(entry->merge_nr);
+
+ for (j = 0; j < entry->split_nr; j++) {
+ unsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));
+
+ if (p >= total_path_nr)
+ goto end;
+
+ /* boundary commit? */
+ if ((paths[p] & IPATH) && entry->uninteresting) {
+ if (last_objects[p]) {
+ parse_commit(last_objects[p]);
+
+ last_objects[p]->object.flags &= ~FACE_VALUE;
+ last_objects[p] = 0;
+ }
+ } else if (last_objects[p] && !last_objects[p]->object.parsed)
+ commit_list_insert(co, &last_objects[p]->parents);
+
+ /* can't close a merge path until all are parents have been encountered */
+ if (GET_COUNT(paths[p])) {
+ SET_COUNT(paths[p], GET_COUNT(paths[p]) - 1);
+
+ if (GET_COUNT(paths[p]))
+ continue;
+ }
+
+ paths[p] = 0;
+ last_objects[p] = 0;
+ }
+ }
+
+ /* make topo relations */
+ if (last_objects[path] && !last_objects[path]->object.parsed)
+ commit_list_insert(co, &last_objects[path]->parents);
+
+ /* initialize commit */
+ if (!entry->is_end) {
+ co->date = entry->date;
+ obj->flags |= ADDED | FACE_VALUE;
+ } else
+ parse_commit(co);
+
+ obj->flags |= SEEN;
+
+ if (entry->uninteresting)
+ obj->flags |= UNINTERESTING;
+
+ /* we need to know what the edges are */
+ last_objects[path] = co;
+
+ /* add to list */
+ if (!(obj->flags & UNINTERESTING) || revs->show_all) {
+ if (entry->is_end)
+ insert_by_date_cached(co, work, insert_cache, &insert_cache);
+ else
+ *queue = &commit_list_insert(co, *queue)->next;
+
+ /* add children to list as well */
+ if (obj->flags & UNINTERESTING)
+ consume_children = 0;
+ else
+ consume_children = 1;
+ }
+
+ /* open parents */
+ if (entry->merge_nr) {
+ int j, off = index + sizeof(struct rc_object_entry_ondisk);
+ char flag = entry->uninteresting ? UPATH : IPATH;
+
+ for (j = 0; j < entry->merge_nr; j++) {
+ unsigned short p = ntohs(*(uint16_t *)(map + off + RC_PATH_SIZE(j)));
+
+ if (p >= total_path_nr)
+ goto end;
+
+ if (paths[p] & flag)
+ continue;
+
+ paths[p] |= flag;
+ }
+
+ /* make sure we don't use this path before all our parents have had their say */
+ SET_COUNT(paths[path], entry->merge_nr);
+ }
+
+ }
+
+ retval = 0;
+
+end:
+ free(paths);
+ free(last_objects);
+
+ return retval;
+}
+
+static int get_cache_slice_header(unsigned char *cache_sha1, unsigned char *map, int len, struct rc_slice_header *head)
+{
+ int t;
+
+ memcpy(head, map, sizeof(struct rc_slice_header));
+ head->ofs_objects = ntohl(head->ofs_objects);
+ head->object_nr = ntohl(head->object_nr);
+ head->size = ntohl(head->size);
+ head->path_nr = ntohs(head->path_nr);
+
+ if (memcmp(head->signature, "REVCACHE", 8))
+ return -1;
+ if (head->version != SUPPORTED_REVCACHE_VERSION)
+ return -2;
+ if (hashcmp(head->sha1, cache_sha1))
+ return -3;
+ t = sizeof(struct rc_slice_header);
+ if (t != head->ofs_objects || t >= len)
+ return -4;
+
+ head->size = len;
+
+ return 0;
+}
+
+int traverse_cache_slice(struct rev_info *revs,
+ unsigned char *cache_sha1, struct commit *commit,
+ unsigned long *date_so_far, int *slop_so_far,
+ struct commit_list ***queue, struct commit_list **work)
+{
+ int fd = -1, retval = -3;
+ struct stat fi;
+ struct rc_slice_header head;
+ struct rev_cache_info *rci;
+ unsigned char *map = MAP_FAILED;
+
+ /* the index should've been loaded already to find cache_sha1, but it's good
+ * to be absolutely sure... */
+ if (!idx_map)
+ init_index();
+ if (!idx_map)
+ return -1;
+
+ /* load options */
+ rci = &revs->rev_cache_info;
+
+ memset(&head, 0, sizeof(struct rc_slice_header));
+
+ fd = open(git_path("rev-cache/%s", sha1_to_hex(cache_sha1)), O_RDONLY);
+ if (fd == -1)
+ goto end;
+ if (fstat(fd, &fi) || fi.st_size < sizeof(struct rc_slice_header))
+ goto end;
+
+ map = xmmap(0, fi.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (map == MAP_FAILED)
+ goto end;
+ if (get_cache_slice_header(cache_sha1, map, fi.st_size, &head))
+ goto end;
+
+ retval = traverse_cache_slice_1(&head, map, revs, commit, date_so_far, slop_so_far, queue, work);
+
+end:
+ if (map != MAP_FAILED)
+ munmap(map, fi.st_size);
+ if (fd != -1)
+ close(fd);
+
+ return retval;
+}
+
+
+
+/* generation */
+
+struct path_track {
+ struct commit *commit;
+ int path; /* for keeping track of children */
+
+ struct path_track *next, *prev;
+};
+
+static unsigned char *paths;
+static int path_nr = 1, path_sz;
+
+static struct path_track *path_track;
+static struct path_track *path_track_alloc;
+
+#define PATH_IN_USE 0x80 /* biggest bit we can get as a char */
+
+static int get_new_path(void)
+{
+ int i;
+
+ for (i = 1; i < path_nr; i++)
+ if (!paths[i])
+ break;
+
+ if (i == path_nr) {
+ if (path_nr >= path_sz) {
+ path_sz += 50;
+ paths = xrealloc(paths, path_sz);
+ memset(paths + path_sz - 50, 0, 50);
+ }
+ path_nr++;
+ }
+
+ paths[i] = PATH_IN_USE;
+ return i;
+}
+
+static void remove_path_track(struct path_track **ppt, char total_free)
+{
+ struct path_track *t = *ppt;
+
+ if (t->next)
+ t->next->prev = t->prev;
+ if (t->prev)
+ t->prev->next = t->next;
+
+ t = t->next;
+
+ if (total_free)
+ free(*ppt);
+ else {
+ (*ppt)->next = path_track_alloc;
+ path_track_alloc = *ppt;
+ }
+
+ *ppt = t;
+}
+
+static struct path_track *make_path_track(struct path_track **head, struct commit *commit)
+{
+ struct path_track *pt;
+
+ if (path_track_alloc) {
+ pt = path_track_alloc;
+ path_track_alloc = pt->next;
+ } else
+ pt = xmalloc(sizeof(struct path_track));
+
+ memset(pt, 0, sizeof(struct path_track));
+ pt->commit = commit;
+
+ pt->next = *head;
+ if (*head)
+ (*head)->prev = pt;
+ *head = pt;
+
+ return pt;
+}
+
+static void add_path_to_track(struct commit *commit, int path)
+{
+ make_path_track(&path_track, commit);
+ path_track->path = path;
+}
+
+static void handle_paths(struct commit *commit, struct rc_object_entry *object, struct strbuf *merge_str, struct strbuf *split_str)
+{
+ int child_nr, parent_nr, open_parent_nr, this_path;
+ struct commit_list *list;
+ struct commit *first_parent;
+ struct path_track **ppt, *pt;
+
+ /* we can only re-use a closed path once all it's children have been encountered,
+ * as we need to keep track of commit boundaries */
+ ppt = &path_track;
+ pt = *ppt;
+ child_nr = 0;
+ while (pt) {
+ if (pt->commit == commit) {
+ uint16_t write_path;
+
+ if (paths[pt->path] != PATH_IN_USE)
+ paths[pt->path]--;
+
+ /* make sure we can handle this */
+ child_nr++;
+ if (child_nr > 0x7f)
+ die("%s: too many branches! rev-cache can only handle %d parents/children per commit",
+ sha1_to_hex(object->sha1), 0x7f);
+
+ /* add to split list */
+ object->split_nr++;
+ write_path = htons((uint16_t)pt->path);
+ strbuf_add(split_str, &write_path, sizeof(uint16_t));
+
+ remove_path_track(ppt, 0);
+ pt = *ppt;
+ } else {
+ pt = pt->next;
+ ppt = &pt;
+ }
+ }
+
+ /* initialize our self! */
+ if (!commit->indegree) {
+ commit->indegree = get_new_path();
+ object->is_start = 1;
+ }
+
+ this_path = commit->indegree;
+ paths[this_path] = PATH_IN_USE;
+ object->path = this_path;
+
+ /* count interesting parents */
+ parent_nr = open_parent_nr = 0;
+ first_parent = 0;
+ for (list = commit->parents; list; list = list->next) {
+ if (list->item->object.flags & UNINTERESTING) {
+ object->is_end = 1;
+ continue;
+ }
+
+ parent_nr++;
+ if (!list->item->indegree)
+ open_parent_nr++;
+ if (!first_parent)
+ first_parent = list->item;
+ }
+
+ if (!parent_nr)
+ return;
+
+ if (parent_nr == 1 && open_parent_nr == 1) {
+ first_parent->indegree = this_path;
+ return;
+ }
+
+ /* bail out on obscene parent/child #s */
+ if (parent_nr > 0x7f)
+ die("%s: too many parents in merge! rev-cache can only handle %d parents/children per commit",
+ sha1_to_hex(object->sha1), 0x7f);
+
+ /* make merge list */
+ object->merge_nr = parent_nr;
+ paths[this_path] = parent_nr;
+
+ for (list = commit->parents; list; list = list->next) {
+ struct commit *p = list->item;
+ uint16_t write_path;
+
+ if (p->object.flags & UNINTERESTING)
+ continue;
+
+ /* unfortunately due to boundary tracking we can't re-use merge paths
+ * (unable to guarantee last parent path = this -> last won't always be able to
+ * set this as a boundary object */
+ if (!p->indegree)
+ p->indegree = get_new_path();
+
+ write_path = htons((uint16_t)p->indegree);
+ strbuf_add(merge_str, &write_path, sizeof(uint16_t));
+
+ /* make sure path is properly ended */
+ add_path_to_track(p, this_path);
+ }
+
+}
+
+
+static void add_object_entry(const unsigned char *sha1, int type, struct rc_object_entry *nothisone,
+ struct strbuf *merge_str, struct strbuf *split_str)
+{
+ struct rc_object_entry object;
+
+ if (!nothisone) {
+ memset(&object, 0, sizeof(object));
+ object.sha1 = (unsigned char *)sha1;
+ object.type = type;
+
+ if (merge_str)
+ object.merge_nr = merge_str->len / sizeof(uint16_t);
+ if (split_str)
+ object.split_nr = split_str->len / sizeof(uint16_t);
+
+ nothisone = &object;
+ }
+
+ strbuf_add(acc_buffer, to_disked_rc_object_entry(nothisone, 0), sizeof(struct rc_object_entry_ondisk));
+
+ if (merge_str && merge_str->len)
+ strbuf_add(acc_buffer, merge_str->buf, merge_str->len);
+ if (split_str && split_str->len)
+ strbuf_add(acc_buffer, split_str->buf, split_str->len);
+
+}
+
+static void init_revcache_directory(void)
+{
+ struct stat fi;
+
+ if (stat(git_path("rev-cache"), &fi) || !S_ISDIR(fi.st_mode))
+ if (mkdir(git_path("rev-cache"), 0666))
+ die("can't make rev-cache directory");
+
+}
+
+void init_rev_cache_info(struct rev_cache_info *rci)
+{
+ rci->objects = 1;
+ rci->legs = 0;
+ rci->make_index = 1;
+
+ rci->add_to_pending = 1;
+
+ rci->ignore_size = 0;
+}
+
+void maybe_fill_with_defaults(struct rev_cache_info *rci)
+{
+ static struct rev_cache_info def_rci;
+
+ if (rci)
+ return;
+
+ init_rev_cache_info(&def_rci);
+ rci = &def_rci;
+}
+
+int make_cache_slice(struct rev_cache_info *rci,
+ struct rev_info *revs, struct commit_list **starts, struct commit_list **ends,
+ unsigned char *cache_sha1)
+{
+ struct rev_info therevs;
+ struct strbuf buffer, startlist, endlist;
+ struct rc_slice_header head;
+ struct commit *commit;
+ unsigned char sha1[20];
+ struct strbuf merge_paths, split_paths;
+ int object_nr, total_sz, fd;
+ char file[PATH_MAX], *newfile;
+ struct rev_cache_info *trci;
+ git_SHA_CTX ctx;
+
+ maybe_fill_with_defaults(rci);
+
+ init_revcache_directory();
+ strcpy(file, git_path("rev-cache/XXXXXX"));
+ fd = xmkstemp(file);
+
+ strbuf_init(&buffer, 0);
+ strbuf_init(&startlist, 0);
+ strbuf_init(&endlist, 0);
+ strbuf_init(&merge_paths, 0);
+ strbuf_init(&split_paths, 0);
+ acc_buffer = &buffer;
+
+ if (!revs) {
+ revs = &therevs;
+ init_revisions(revs, 0);
+
+ /* we're gonna assume no one else has already traversed this... */
+ while ((commit = pop_commit(starts)))
+ add_pending_object(revs, &commit->object, 0);
+
+ while ((commit = pop_commit(ends))) {
+ commit->object.flags |= UNINTERESTING;
+ add_pending_object(revs, &commit->object, 0);
+ }
+ }
+
+ /* write head placeholder */
+ memset(&head, 0, sizeof(head));
+ head.ofs_objects = htonl(sizeof(head));
+ xwrite(fd, &head, sizeof(head));
+
+ /* init revisions! */
+ revs->tree_objects = 1;
+ revs->blob_objects = 1;
+ revs->topo_order = 1;
+ revs->lifo = 1;
+
+ /* re-use info from other caches if possible */
+ trci = &revs->rev_cache_info;
+ init_rev_cache_info(trci);
+ trci->add_to_pending = 0;
+
+ setup_revisions(0, 0, revs, 0);
+ if (prepare_revision_walk(revs))
+ die("died preparing revision walk");
+
+ object_nr = total_sz = 0;
+ while ((commit = get_revision(revs)) != 0) {
+ struct rc_object_entry object;
+
+ strbuf_setlen(&merge_paths, 0);
+ strbuf_setlen(&split_paths, 0);
+
+ memset(&object, 0, sizeof(object));
+ object.type = OBJ_COMMIT;
+ object.date = commit->date;
+ object.sha1 = commit->object.sha1;
+
+ handle_paths(commit, &object, &merge_paths, &split_paths);
+
+ if (object.is_end) {
+ strbuf_add(&endlist, object.sha1, 20);
+ if (ends)
+ commit_list_insert(commit, ends);
+ }
+ /* the two *aren't* mutually exclusive */
+ if (object.is_start) {
+ strbuf_add(&startlist, object.sha1, 20);
+ if (starts)
+ commit_list_insert(commit, starts);
+ }
+
+ commit->indegree = 0;
+
+ add_object_entry(0, 0, &object, &merge_paths, &split_paths);
+ object_nr++;
+
+ /* print every ~1MB or so */
+ if (buffer.len > 1000000) {
+ write_in_full(fd, buffer.buf, buffer.len);
+ total_sz += buffer.len;
+
+ strbuf_setlen(&buffer, 0);
+ }
+ }
+
+ if (buffer.len) {
+ write_in_full(fd, buffer.buf, buffer.len);
+ total_sz += buffer.len;
+ }
+
+ /* go ahead a free some stuff... */
+ strbuf_release(&buffer);
+ strbuf_release(&merge_paths);
+ strbuf_release(&split_paths);
+ if (path_sz)
+ free(paths);
+ while (path_track_alloc)
+ remove_path_track(&path_track_alloc, 1);
+
+ /* the meaning of the hash name is more or less irrelevant, it's the uniqueness that matters */
+ strbuf_add(&endlist, startlist.buf, startlist.len);
+ git_SHA1_Init(&ctx);
+ git_SHA1_Update(&ctx, endlist.buf, endlist.len);
+ git_SHA1_Final(sha1, &ctx);
+
+ /* now actually initialize header */
+ strcpy(head.signature, "REVCACHE");
+ head.version = SUPPORTED_REVCACHE_VERSION;
+
+ head.object_nr = htonl(object_nr);
+ head.size = htonl(ntohl(head.ofs_objects) + total_sz);
+ head.path_nr = htons(path_nr);
+ hashcpy(head.sha1, sha1);
+
+ /* some info! */
+ fprintf(stderr, "objects: %d\n", object_nr);
+ fprintf(stderr, "paths: %d\n", path_nr);
+
+ lseek(fd, 0, SEEK_SET);
+ xwrite(fd, &head, sizeof(head));
+
+ if (rci->make_index && make_cache_index(rci, sha1, fd, ntohl(head.size)) < 0)
+ die("can't update index");
+
+ close(fd);
+
+ newfile = git_path("rev-cache/%s", sha1_to_hex(sha1));
+ if (rename(file, newfile))
+ die("can't move temp file");
+
+ /* let our caller know what we've just made */
+ if (cache_sha1)
+ hashcpy(cache_sha1, sha1);
+
+ strbuf_release(&endlist);
+ strbuf_release(&startlist);
+
+ return 0;
+}
+
+
+static int index_sort_hash(const void *a, const void *b)
+{
+ return hashcmp(((struct rc_index_entry_ondisk *)a)->sha1, ((struct rc_index_entry_ondisk *)b)->sha1);
+}
+
+static int write_cache_index(struct strbuf *body)
+{
+ struct rc_index_header whead;
+ struct lock_file *lk;
+ int fd, i;
+
+ /* clear index map if loaded */
+ if (idx_map) {
+ munmap(idx_map, idx_size);
+ idx_map = 0;
+ }
+
+ lk = xcalloc(sizeof(struct lock_file), 1);
+ fd = hold_lock_file_for_update(lk, git_path("rev-cache/index"), 0);
+ if (fd < 0) {
+ free(lk);
+ return -1;
+ }
+
+ /* endianness yay! */
+ memset(&whead, 0, sizeof(whead));
+ memcpy(whead.signature, "REVINDEX", 8);
+ whead.version = idx_head.version;
+ whead.ofs_objects = htonl(idx_head.ofs_objects);
+ whead.object_nr = htonl(idx_head.object_nr);
+ whead.cache_nr = idx_head.cache_nr;
+ whead.max_date = htonl(idx_head.max_date);
+
+ write(fd, &whead, sizeof(struct rc_index_header));
+ write_in_full(fd, idx_caches, idx_head.cache_nr * 20);
+
+ for (i = 0; i <= 0xff; i++)
+ fanout[i] = htonl(fanout[i]);
+ write_in_full(fd, fanout, 0x100 * sizeof(uint32_t));
+
+ write_in_full(fd, body->buf, body->len);
+
+ if (commit_lock_file(lk) < 0)
+ return -2;
+
+ /* lk freed by lockfile.c */
+
+ return 0;
+}
+
+int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,
+ int fd, unsigned int size)
+{
+ struct strbuf buffer;
+ int i, cache_index, cur;
+ unsigned char *map;
+ unsigned long max_date;
+
+ if (!idx_map)
+ init_index();
+
+ lseek(fd, 0, SEEK_SET);
+ map = xmmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (map == MAP_FAILED)
+ return -1;
+
+ strbuf_init(&buffer, 0);
+ if (idx_map) {
+ strbuf_add(&buffer, idx_map + fanout[0], fanout[0x100] - fanout[0]);
+ } else {
+ /* not an update */
+ memset(&idx_head, 0, sizeof(struct rc_index_header));
+ idx_caches = 0;
+
+ strcpy(idx_head.signature, "REVINDEX");
+ idx_head.version = SUPPORTED_REVINDEX_VERSION;
+ idx_head.ofs_objects = sizeof(struct rc_index_header) + 0x100 * sizeof(uint32_t);
+ }
+
+ /* are we remaking a slice? */
+ for (i = 0; i < idx_head.cache_nr; i++)
+ if (!hashcmp(idx_caches + i * 20, cache_sha1))
+ break;
+
+ if (i == idx_head.cache_nr) {
+ cache_index = idx_head.cache_nr++;
+ idx_head.ofs_objects += 20;
+
+ idx_caches = xrealloc(idx_caches, idx_head.cache_nr * 20);
+ hashcpy(idx_caches + cache_index * 20, cache_sha1);
+ } else
+ cache_index = i;
+
+ i = sizeof(struct rc_slice_header); /* offset */
+ max_date = idx_head.max_date;
+ while (i < size) {
+ struct rc_index_entry index_entry, *entry;
+ struct rc_index_entry_ondisk *disked_entry;
+ struct rc_object_entry *object_entry = RC_OBTAIN_OBJECT_ENTRY(map + i);
+ unsigned long date;
+ int off, pos = i;
+
+ i += RC_ACTUAL_OBJECT_ENTRY_SIZE(object_entry);
+
+ if (object_entry->type != OBJ_COMMIT)
+ continue;
+
+ /* don't include ends; otherwise we'll find ourselves in loops */
+ if (object_entry->is_end)
+ continue;
+
+ /* handle index duplication
+ * -> keep old copy unless new one is a start -- based on expected usage, older ones will be more
+ * likely to lead to greater slice traversals than new ones */
+ date = object_entry->date;
+ if (date > idx_head.max_date) {
+ disked_entry = 0;
+ if (date > max_date)
+ max_date = date;
+ } else
+ disked_entry = search_index_1(object_entry->sha1);
+
+ if (disked_entry && !object_entry->is_start)
+ continue;
+ else if (disked_entry) {
+ /* mmm, pointer arithmetic... tasty */ /* (entry - idx_map = offset, so cast is valid) */
+ off = (unsigned int)((unsigned char *)disked_entry - idx_map) - fanout[0];
+ disked_entry = (struct rc_index_entry_ondisk *)(buffer.buf + off);
+ entry = from_disked_rc_index_entry(disked_entry, 0);
+ } else
+ entry = &index_entry;
+
+ memset(entry, 0, sizeof(index_entry));
+ entry->sha1 = object_entry->sha1;
+ entry->is_start = object_entry->is_start;
+ entry->cache_index = cache_index;
+ entry->pos = pos;
+
+ if (entry == &index_entry) {
+ strbuf_add(&buffer, to_disked_rc_index_entry(entry, 0), sizeof(struct rc_index_entry_ondisk));
+ idx_head.object_nr++;
+ } else
+ to_disked_rc_index_entry(entry, disked_entry);
+
+ }
+
+ idx_head.max_date = max_date;
+ qsort(buffer.buf, buffer.len / sizeof(struct rc_index_entry_ondisk), sizeof(struct rc_index_entry_ondisk), index_sort_hash);
+
+ /* generate fanout */
+ cur = 0x00;
+ for (i = 0; i < buffer.len; i += sizeof(struct rc_index_entry_ondisk)) {
+ struct rc_index_entry_ondisk *entry = (struct rc_index_entry_ondisk *)(buffer.buf + i);
+
+ while (cur <= entry->sha1[0])
+ fanout[cur++] = i + idx_head.ofs_objects;
+ }
+
+ while (cur <= 0xff)
+ fanout[cur++] = idx_head.ofs_objects + buffer.len;
+
+ /* BOOM! */
+ if (write_cache_index(&buffer))
+ return -1;
+
+ munmap(map, size);
+ strbuf_release(&buffer);
+
+ /* idx_map is unloaded without cleanup_cache_slices(), so regardless of previous index existence
+ * we can still free this up */
+ free(idx_caches);
+
+ return 0;
+}
+
+
+/* add start-commits from each cache slice (uninterestingness will be propogated) */
+void starts_from_slices(struct rev_info *revs, unsigned int flags)
+{
+ struct commit *commit;
+ int i;
+
+ if (!idx_map)
+ init_index();
+ if (!idx_map)
+ return;
+
+ for (i = idx_head.ofs_objects; i < idx_size; i += sizeof(struct rc_index_entry_ondisk)) {
+ struct rc_index_entry *entry = RC_OBTAIN_INDEX_ENTRY(idx_map + i);
+
+ if (!entry->is_start)
+ continue;
+
+ commit = lookup_commit(entry->sha1);
+ if (!commit)
+ continue;
+
+ commit->object.flags |= flags;
+ add_pending_object(revs, &commit->object, 0);
+ }
+
+}
diff --git a/rev-cache.h b/rev-cache.h
new file mode 100644
index 0000000..a471fbf
--- /dev/null
+++ b/rev-cache.h
@@ -0,0 +1,107 @@
+#ifndef REV_CACHE_H
+#define REV_CACHE_H
+
+#define SUPPORTED_REVCACHE_VERSION 1
+#define SUPPORTED_REVINDEX_VERSION 1
+
+#define RC_PATH_SIZE(x) (sizeof(uint16_t) * (x))
+
+#define RC_OBTAIN_OBJECT_ENTRY(p) from_disked_rc_object_entry((struct rc_object_entry_ondisk *)(p), 0)
+#define RC_OBTAIN_INDEX_ENTRY(p) from_disked_rc_index_entry((struct rc_index_entry_ondisk *)(p), 0)
+
+#define RC_ACTUAL_OBJECT_ENTRY_SIZE(e) (sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE((e)->merge_nr + (e)->split_nr) + (e)->size_size)
+
+/* single index maps objects to cache files */
+struct rc_index_header {
+ char signature[8]; /* REVINDEX */
+ unsigned char version;
+ uint32_t ofs_objects;
+
+ uint32_t object_nr;
+ unsigned char cache_nr;
+
+ uint32_t max_date;
+};
+
+struct rc_index_entry_ondisk {
+ unsigned char sha1[20];
+ unsigned char flags;
+ uint32_t pos;
+};
+
+struct rc_index_entry {
+ unsigned char *sha1;
+ unsigned is_start : 1;
+ unsigned cache_index : 7;
+ uint32_t pos;
+};
+
+
+/* structure for actual cache file */
+struct rc_slice_header {
+ char signature[8]; /* REVCACHE */
+ unsigned char version;
+ uint32_t ofs_objects;
+
+ uint32_t object_nr;
+ uint16_t path_nr;
+ uint32_t size;
+
+ unsigned char sha1[20];
+};
+
+struct rc_object_entry_ondisk {
+ unsigned char flags;
+ unsigned char sha1[20];
+
+ unsigned char merge_nr;
+ unsigned char split_nr;
+ unsigned char sizes;
+
+ uint32_t date;
+ uint16_t path;
+};
+
+struct rc_object_entry {
+ unsigned type : 3;
+ unsigned is_end : 1;
+ unsigned is_start : 1;
+ unsigned uninteresting : 1;
+ unsigned include : 1;
+ unsigned flag : 1; /* unused */
+ unsigned char *sha1; /* 20 byte */
+
+ unsigned char merge_nr; /* : 7 */
+ unsigned char split_nr; /* : 7 */
+ unsigned size_size : 3;
+ unsigned padding : 5;
+
+ uint32_t date;
+ uint16_t path;
+
+ /* merge paths */
+ /* split paths */
+ /* size */
+};
+
+struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst);
+struct rc_index_entry_ondisk *to_disked_rc_index_entry(struct rc_index_entry *src, struct rc_index_entry_ondisk *dst);
+struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondisk *src, struct rc_object_entry *dst);
+struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst);
+
+extern unsigned char *get_cache_slice(struct commit *commit);
+extern int traverse_cache_slice(struct rev_info *revs,
+ unsigned char *cache_sha1, struct commit *commit,
+ unsigned long *date_so_far, int *slop_so_far,
+ struct commit_list ***queue, struct commit_list **work);
+
+extern void init_rev_cache_info(struct rev_cache_info *rci);
+extern int make_cache_slice(struct rev_cache_info *rci,
+ struct rev_info *revs, struct commit_list **starts, struct commit_list **ends,
+ unsigned char *cache_sha1);
+extern int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,
+ int fd, unsigned int size);
+
+extern void starts_from_slices(struct rev_info *revs, unsigned int flags);
+
+#endif
diff --git a/revision.c b/revision.c
index 9f5dac5..485bf72 100644
--- a/revision.c
+++ b/revision.c
@@ -432,7 +432,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
commit->object.flags |= TREESAME;
}
-static void insert_by_date_cached(struct commit *p, struct commit_list **head,
+void insert_by_date_cached(struct commit *p, struct commit_list **head,
struct commit_list *cached_base, struct commit_list **cache)
{
struct commit_list *new_entry;
diff --git a/revision.h b/revision.h
index fb74492..e767a43 100644
--- a/revision.h
+++ b/revision.h
@@ -13,11 +13,25 @@
#define CHILD_SHOWN (1u<<6)
#define ADDED (1u<<7) /* Parents already parsed and added? */
#define SYMMETRIC_LEFT (1u<<8)
-#define ALL_REV_FLAGS ((1u<<9)-1)
+#define FACE_VALUE (1u<<9)
+#define ALL_REV_FLAGS ((1u<<10)-1)
struct rev_info;
struct log_info;
+struct rev_cache_info {
+ /* generation flags */
+ unsigned objects : 1,
+ legs : 1,
+ make_index : 1;
+
+ /* traversal flags */
+ unsigned add_to_pending : 1;
+
+ /* fuse options */
+ unsigned int ignore_size;
+};
+
struct rev_info {
/* Starting list */
struct commit_list *commits;
@@ -73,6 +87,10 @@ struct rev_info {
dense_combined_merges:1,
always_show_header:1;
+ /* rev-cache flags */
+ unsigned int for_pack:1,
+ dont_cache_me:1;
+
/* Format info */
unsigned int shown_one:1,
show_merge:1,
@@ -116,6 +134,9 @@ struct rev_info {
struct reflog_walk_info *reflog_info;
struct decoration children;
struct decoration merge_simplification;
+
+ /* caching info, used ONLY by traverse_cache_slice */
+ struct rev_cache_info rev_cache_info;
};
#define REV_TREE_SAME 0
@@ -167,4 +188,7 @@ enum commit_action {
extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit);
+extern void insert_by_date_cached(struct commit *p, struct commit_list **head,
+ struct commit_list *cached_base, struct commit_list **cache);
+
#endif
diff --git a/t/t6015-rev-cache-list.sh b/t/t6015-rev-cache-list.sh
new file mode 100755
index 0000000..e7474fd
--- /dev/null
+++ b/t/t6015-rev-cache-list.sh
@@ -0,0 +1,104 @@
+#!/bin/sh
+
+test_description='git rev-cache tests'
+. ./test-lib.sh
+
+test_cmp_sorted() {
+ grep -io "[a-f0-9]*" $1 | sort >.tmpfile1 &&
+ grep -io "[a-f0-9]*" $2 | sort >.tmpfile2 &&
+ test_cmp .tmpfile1 .tmpfile2
+}
+
+# we want a totally wacked out branch structure...
+# we need branching and merging of sizes up through 3, tree
+# addition/deletion, and enough branching to exercise path
+# reuse
+test_expect_success 'init repo' '
+ echo bla >file &&
+ git add . &&
+ git commit -m "bla" &&
+
+ git branch b1 &&
+ git checkout b1 &&
+ echo blu >file2 &&
+ mkdir d1 &&
+ echo bang >d1/filed1 &&
+ git add . &&
+ git commit -m "blu" &&
+
+ git checkout master &&
+ git branch b2 &&
+ git checkout b2 &&
+ echo kaplaa >>file &&
+ git commit -a -m "kaplaa" &&
+
+ git checkout master &&
+ mkdir smoke &&
+ echo omg >smoke/bong &&
+ git add . &&
+ git commit -m "omg" &&
+
+ git branch b4 &&
+ git checkout b4 &&
+ echo shazam >file8 &&
+ git add . &&
+ git commit -m "shazam" &&
+ git merge -m "merge b2" b2 &&
+
+ echo bam >smoke/pipe &&
+ git add .
+ git commit -m "bam" &&
+
+ git checkout master &&
+ echo pow >file7 &&
+ git add . &&
+ git commit -m "pow" &&
+ git merge -m "merge b4" b4 &&
+
+ git checkout b1 &&
+ echo stuff >d1/filed1 &&
+ git commit -a -m "stuff" &&
+
+ git branch b11 &&
+ git checkout b11 &&
+ echo wazzup >file3 &&
+ git add file3 &&
+ git commit -m "wazzup" &&
+
+ git checkout b1 &&
+ mkdir d1/d2 &&
+ echo lol >d1/d2/filed2 &&
+ git add . &&
+ git commit -m "lol" &&
+
+ git checkout master &&
+ git merge -m "triple merge" b1 b11 &&
+ git rm -r d1 &&
+ git commit -a -m "oh noes"
+'
+
+git-rev-list HEAD --not HEAD~3 >proper_commit_list_limited
+git-rev-list HEAD >proper_commit_list
+
+test_expect_success 'make cache slice' '
+ git-rev-cache add HEAD 2>output.err &&
+ grep "final return value: 0" output.err
+'
+
+test_expect_success 'remake cache slice' '
+ git-rev-cache add HEAD 2>output.err &&
+ grep "final return value: 0" output.err
+'
+
+#check core mechanics and rev-list hook for commits
+test_expect_success 'test rev-caches walker directly (limited)' '
+ git-rev-cache walk HEAD --not HEAD~3 >list &&
+ test_cmp_sorted list proper_commit_list_limited
+'
+
+test_expect_success 'test rev-caches walker directly (unlimited)' '
+ git-rev-cache walk HEAD >list &&
+ test_cmp_sorted list proper_commit_list
+'
+
+test_done
--
tg: (6ffd781..) t/revcache/basic (depends on: master)
^ permalink raw reply related
* Re: [PATCH 4/6 (v2)] administrative functions for rev-cache, and start of integration into git
From: Nick Edelen @ 2009-08-16 22:42 UTC (permalink / raw)
To: Nick Edelen, Junio C Hamano, Nicolas Pitre, Johannes Schindelin,
Sam Vilain
In-Reply-To: <op.uyb1vcvytdk399@sirnot.private>
This patch, fourth, contains miscellaneous (maintenance) features:
- support for cache slice fusion, index regeneration and object size caching
- non-commit object generation refactored to take advantage of 'size' field
- porcelain updated to support feature additions
The beginnings of integration into git are present in this patch, mainly
centered on caching object size; the object generation is refactored to more
elegantly exploit this. Fusion allows smaller (incremental) slices to be
coagulated into a larger slice, reducing overhead, while index regeneration
enables repair or cleaning of the cache index.
Note that tests for these features are included in the following patch, as they
take advantage of the rev-cache's integration into the revision walker.
Signed-off-by: Nick Edelen <sirnot@gmail.com>
---
builtin-gc.c | 9 +
builtin-rev-cache.c | 77 ++++++-
rev-cache.c | 717 +++++++++++++++++++++++++++++++++++++++++++++------
rev-cache.h | 9 +-
revision.h | 16 +-
5 files changed, 749 insertions(+), 79 deletions(-)
diff --git a/builtin-gc.c b/builtin-gc.c
index 7d3e9cc..c92511a 100644
--- a/builtin-gc.c
+++ b/builtin-gc.c
@@ -22,6 +22,7 @@ static const char * const builtin_gc_usage[] = {
NULL
};
+static char do_rev_cache = 0;
static int pack_refs = 1;
static int aggressive_window = 250;
static int gc_auto_threshold = 6700;
@@ -34,9 +35,14 @@ static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL};
static const char *argv_prune[] = {"prune", "--expire", NULL, NULL};
static const char *argv_rerere[] = {"rerere", "gc", NULL};
+static const char *argv_rev_cache[] = {"rev-cache", "fuse", "--all", "--ignore-size", NULL};
static int gc_config(const char *var, const char *value, void *cb)
{
+ if (!strcmp(var, "gc.revcache")) {
+ do_rev_cache = 1;
+ return 0;
+ }
if (!strcmp(var, "gc.packrefs")) {
if (value && !strcmp(value, "notbare"))
pack_refs = -1;
@@ -244,6 +250,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
if (run_command_v_opt(argv_rerere, RUN_GIT_CMD))
return error(FAILED_RUN, argv_rerere[0]);
+ if (do_rev_cache && run_command_v_opt(argv_rev_cache, RUN_GIT_CMD))
+ return error(FAILED_RUN, argv_rev_cache[0]);
+
if (auto_gc && too_many_loose_objects())
warning("There are too many unreachable loose objects; "
"run 'git prune' to remove them.");
diff --git a/builtin-rev-cache.c b/builtin-rev-cache.c
index c27e0f3..7ca39c4 100644
--- a/builtin-rev-cache.c
+++ b/builtin-rev-cache.c
@@ -5,6 +5,8 @@
#include "revision.h"
#include "rev-cache.h"
+unsigned long default_ignore_size = 50 * 1024 * 1024; /* 50mb */
+
/* porcelain for rev-cache.c */
static int handle_add(int argc, const char *argv[]) /* args beyond this command */
{
@@ -24,7 +26,7 @@ static int handle_add(int argc, const char *argv[]) /* args beyond this command
if (!strcmp(argv[i], "--stdin"))
dostdin = 1;
else if (!strcmp(argv[i], "--fresh"))
- starts_from_slices(&revs, UNINTERESTING);
+ starts_from_slices(&revs, UNINTERESTING, 0, 0);
else if (!strcmp(argv[i], "--not"))
flags ^= UNINTERESTING;
else if (!strcmp(argv[i], "--legs"))
@@ -150,6 +152,57 @@ static int handle_walk(int argc, const char *argv[])
return 0;
}
+static int handle_fuse(int argc, const char *argv[])
+{
+ struct rev_info revs;
+ struct rev_cache_info rci;
+ const char *args[5];
+ int i, argn = 0;
+ char add_all = 0;
+
+ init_revisions(&revs, 0);
+ init_rev_cache_info(&rci);
+ args[argn++] = "rev-list";
+
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "--all")) {
+ args[argn++] = "--all";
+ setup_revisions(argn, args, &revs, 0);
+ add_all = 1;
+ } else if (!strcmp(argv[i], "--no-objects"))
+ rci.objects = 0;
+ else if (!strncmp(argv[i], "--ignore-size", 13)) {
+ unsigned long sz;
+
+ if (argv[i][13] == '=')
+ git_parse_ulong(argv[i] + 14, &sz);
+ else
+ sz = default_ignore_size;
+
+ rci.ignore_size = sz;
+ } else
+ continue;
+ }
+
+ if (!add_all)
+ starts_from_slices(&revs, 0, 0, 0);
+
+ return fuse_cache_slices(&rci, &revs);
+}
+
+static int handle_index(int argc, const char *argv[])
+{
+ return regenerate_cache_index(0);
+}
+
+static int handle_alt(int argc, const char *argv[])
+{
+ if (argc < 1)
+ return -1;
+
+ return make_cache_slice_pointer(0, argv[0]);
+}
+
static int handle_help(void)
{
char *usage = "\
@@ -179,12 +232,28 @@ commands:\n\
return 0;
}
+static int rev_cache_config(const char *k, const char *v, void *cb)
+{
+ /* this could potentially be related to pack.windowmemory, but we want a max around 50mb,
+ * and .windowmemory is often >700mb, with *large* variations */
+ if (!strcmp(k, "revcache.ignoresize")) {
+ int t;
+
+ t = git_config_ulong(k, v);
+ if (t)
+ default_ignore_size = t;
+ }
+
+ return 0;
+}
+
int cmd_rev_cache(int argc, const char *argv[], const char *prefix)
{
const char *arg;
int r;
git_config(git_default_config, NULL);
+ git_config(rev_cache_config, NULL);
if (argc > 1)
arg = argv[1];
@@ -195,8 +264,14 @@ int cmd_rev_cache(int argc, const char *argv[], const char *prefix)
argv += 2;
if (!strcmp(arg, "add"))
r = handle_add(argc, argv);
+ else if (!strcmp(arg, "fuse"))
+ r = handle_fuse(argc, argv);
else if (!strcmp(arg, "walk"))
r = handle_walk(argc, argv);
+ else if (!strcmp(arg, "index"))
+ r = handle_index(argc, argv);
+ else if (!strcmp(arg, "alt"))
+ r = handle_alt(argc, argv);
else
return handle_help();
diff --git a/rev-cache.c b/rev-cache.c
index ddcc596..8eac1f0 100644
--- a/rev-cache.c
+++ b/rev-cache.c
@@ -9,6 +9,13 @@
#include "revision.h"
#include "rev-cache.h"
#include "run-command.h"
+#include "string-list.h"
+
+struct cache_slice_pointer {
+ char signature[8]; /* REVCOPTR */
+ char version;
+ char path[PATH_MAX + 1];
+};
/* list resembles pack index format */
static uint32_t fanout[0xff + 2];
@@ -257,27 +264,45 @@ unsigned char *get_cache_slice(struct commit *commit)
/* traversal */
-static void handle_noncommit(struct rev_info *revs, unsigned char *ptr, struct rc_object_entry *entry)
+static unsigned long decode_size(unsigned char *str, int len);
+
+static void handle_noncommit(struct rev_info *revs, struct commit *commit, unsigned char *ptr, struct rc_object_entry *entry)
{
- struct object *obj = 0;
+ struct blob *blob;
+ struct tree *tree;
+ struct object *obj;
+ unsigned long size;
+ size = decode_size(ptr + RC_ENTRY_SIZE_OFFSET(entry), entry->size_size);
switch (entry->type) {
case OBJ_TREE :
- if (revs->tree_objects)
- obj = (struct object *)lookup_tree(entry->sha1);
+ if (!revs->tree_objects)
+ return;
+
+ tree = lookup_tree(entry->sha1);
+ if (!tree)
+ return;
+
+ tree->size = size;
+ commit->tree = tree;
+ obj = (struct object *)tree;
break;
+
case OBJ_BLOB :
- if (revs->blob_objects)
- obj = (struct object *)lookup_blob(entry->sha1);
- break;
- case OBJ_TAG :
- if (revs->tag_objects)
- obj = (struct object *)lookup_tag(entry->sha1);
+ if (!revs->blob_objects)
+ return;
+
+ blob = lookup_blob(entry->sha1);
+ if (!blob)
+ return;
+
+ obj = (struct object *)blob;
break;
- }
- if (!obj)
+ default :
+ /* tag objects aren't really supposed to be here */
return;
+ }
obj->flags |= FACE_VALUE;
add_pending_object(revs, obj, "");
@@ -373,7 +398,7 @@ static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *m
/* add extra objects if necessary */
if (entry->type != OBJ_COMMIT) {
if (consume_children)
- handle_noncommit(revs, map + index, entry);
+ handle_noncommit(revs, co, map + index, entry);
continue;
} else
@@ -407,6 +432,8 @@ static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *m
if (last_objects[path]) {
parse_commit(last_objects[path]);
+ /* we needn't worry about the unique field; that will be valid as
+ * long as we're not a end entry */
last_objects[path]->object.flags &= ~FACE_VALUE;
last_objects[path] = 0;
}
@@ -539,6 +566,48 @@ static int get_cache_slice_header(unsigned char *cache_sha1, unsigned char *map,
return 0;
}
+int open_cache_slice(unsigned char *sha1, int flags)
+{
+ int fd;
+ char signature[8];
+
+ fd = open(git_path("rev-cache/%s", sha1_to_hex(sha1)), flags);
+ if (fd <= 0)
+ goto end;
+
+ if (read(fd, signature, 8) != 8)
+ goto end;
+
+ /* a normal revision slice */
+ if (!memcmp(signature, "REVCACHE", 8)) {
+ lseek(fd, 0, SEEK_SET);
+ return fd;
+ }
+
+ /* slice pointer */
+ if (!memcmp(signature, "REVCOPTR", 8)) {
+ struct cache_slice_pointer ptr;
+
+ lseek(fd, 0, SEEK_SET);
+ if (read_in_full(fd, &ptr, sizeof(ptr)) != sizeof(ptr))
+ goto end;
+
+ if (ptr.version != SUPPORTED_REVCOPTR_VERSION)
+ goto end;
+
+ close(fd);
+ fd = open(ptr.path, flags);
+
+ return fd;
+ }
+
+end:
+ if (fd > 0)
+ close(fd);
+
+ return -1;
+}
+
int traverse_cache_slice(struct rev_info *revs,
unsigned char *cache_sha1, struct commit *commit,
unsigned long *date_so_far, int *slop_so_far,
@@ -562,7 +631,7 @@ int traverse_cache_slice(struct rev_info *revs,
memset(&head, 0, sizeof(struct rc_slice_header));
- fd = open(git_path("rev-cache/%s", sha1_to_hex(cache_sha1)), O_RDONLY);
+ fd = open_cache_slice(cache_sha1, O_RDONLY);
if (fd == -1)
goto end;
if (fstat(fd, &fi) || fi.st_size < sizeof(struct rc_slice_header))
@@ -589,6 +658,68 @@ end:
/* generation */
+static int is_endpoint(struct commit *commit)
+{
+ struct commit_list *list = commit->parents;
+
+ while (list) {
+ if (!(list->item->object.flags & UNINTERESTING))
+ return 0;
+
+ list = list->next;
+ }
+
+ return 1;
+}
+
+/* ensures branch is self-contained: parents are either all interesting or all uninteresting */
+static void make_legs(struct rev_info *revs)
+{
+ struct commit_list *list, **plist;
+ int total = 0;
+
+ /* attach plist to end of commits list */
+ list = revs->commits;
+ while (list && list->next)
+ list = list->next;
+
+ if (list)
+ plist = &list->next;
+ else
+ return;
+
+ /* duplicates don't matter, as get_revision() ignores them */
+ for (list = revs->commits; list; list = list->next) {
+ struct commit *item = list->item;
+ struct commit_list *parents = item->parents;
+
+ if (item->object.flags & UNINTERESTING)
+ continue;
+ if (is_endpoint(item))
+ continue;
+
+ while (parents) {
+ struct commit *p = parents->item;
+ parents = parents->next;
+
+ if (!(p->object.flags & UNINTERESTING))
+ continue;
+
+ p->object.flags &= ~UNINTERESTING;
+ parse_commit(p);
+ plist = &commit_list_insert(p, plist)->next;
+
+ if (!(p->object.flags & SEEN))
+ total++;
+ }
+ }
+
+ if (total)
+ sort_in_topological_order(&revs->commits, 1);
+
+}
+
+
struct path_track {
struct commit *commit;
int path; /* for keeping track of children */
@@ -777,31 +908,76 @@ static void handle_paths(struct commit *commit, struct rc_object_entry *object,
}
-static void add_object_entry(const unsigned char *sha1, int type, struct rc_object_entry *nothisone,
+static int encode_size(unsigned long size, unsigned char *out)
+{
+ int len = 0;
+
+ while (size) {
+ *out++ = (unsigned char)(size & 0xff);
+ size >>= 8;
+ len++;
+ }
+
+ return len;
+}
+
+static unsigned long decode_size(unsigned char *str, int len)
+{
+ unsigned long size = 0;
+ int shift = 0;
+
+ while (len--) {
+ size |= (unsigned long)*str << shift;
+ shift += 8;
+ str++;
+ }
+
+ return size;
+}
+
+static void add_object_entry(const unsigned char *sha1, struct rc_object_entry *entryp,
struct strbuf *merge_str, struct strbuf *split_str)
{
- struct rc_object_entry object;
+ struct rc_object_entry entry;
+ unsigned char size_str[7];
+ unsigned long size;
+ enum object_type type;
+ void *data;
- if (!nothisone) {
- memset(&object, 0, sizeof(object));
- object.sha1 = (unsigned char *)sha1;
- object.type = type;
+ if (entryp)
+ sha1 = entryp->sha1;
+
+ /* retrieve size data */
+ data = read_sha1_file(sha1, &type, &size);
+
+ if (data)
+ free(data);
+
+ /* initialize! */
+ if (!entryp) {
+ memset(&entry, 0, sizeof(entry));
+ entry.sha1 = (unsigned char *)sha1;
+ entry.type = type;
if (merge_str)
- object.merge_nr = merge_str->len / sizeof(uint16_t);
+ entry.merge_nr = merge_str->len / sizeof(uint16_t);
if (split_str)
- object.split_nr = split_str->len / sizeof(uint16_t);
+ entry.split_nr = split_str->len / sizeof(uint16_t);
- nothisone = &object;
+ entryp = &entry;
}
- strbuf_add(acc_buffer, to_disked_rc_object_entry(nothisone, 0), sizeof(struct rc_object_entry_ondisk));
+ entryp->size_size = encode_size(size, size_str);
- if (merge_str && merge_str->len)
+ /* write the muvabitch */
+ strbuf_add(acc_buffer, to_disked_rc_object_entry(entryp, 0), sizeof(struct rc_object_entry_ondisk));
+
+ if (merge_str)
strbuf_add(acc_buffer, merge_str->buf, merge_str->len);
- if (split_str && split_str->len)
+ if (split_str)
strbuf_add(acc_buffer, split_str->buf, split_str->len);
+ strbuf_add(acc_buffer, size_str, entryp->size_size);
}
/* returns non-zero to continue parsing, 0 to skip */
@@ -845,12 +1021,7 @@ continue_loop:
static int dump_tree_callback(const unsigned char *sha1, const char *path, unsigned int mode)
{
- unsigned char data[21];
-
- hashcpy(data, sha1);
- data[20] = !!S_ISDIR(mode);
-
- strbuf_add(acc_buffer, data, 21);
+ strbuf_add(acc_buffer, sha1, 20);
return 1;
}
@@ -860,15 +1031,10 @@ static void tree_addremove(struct diff_options *options,
const unsigned char *sha1,
const char *concatpath)
{
- unsigned char data[21];
-
if (whatnow != '+')
return;
- hashcpy(data, sha1);
- data[20] = !!S_ISDIR(mode);
-
- strbuf_add(acc_buffer, data, 21);
+ strbuf_add(acc_buffer, sha1, 20);
}
static void tree_change(struct diff_options *options,
@@ -877,26 +1043,10 @@ static void tree_change(struct diff_options *options,
const unsigned char *new_sha1,
const char *concatpath)
{
- unsigned char data[21];
-
if (!hashcmp(old_sha1, new_sha1))
return;
- hashcpy(data, new_sha1);
- data[20] = !!S_ISDIR(new_mode);
-
- strbuf_add(acc_buffer, data, 21);
-}
-
-static int sort_type_hash(const void *a, const void *b)
-{
- const unsigned char *sa = (const unsigned char *)a,
- *sb = (const unsigned char *)b;
-
- if (sa[20] == sb[20])
- return hashcmp(sa, sb);
-
- return sa[20] > sb[20] ? -1 : 1;
+ strbuf_add(acc_buffer, new_sha1, 20);
}
static int add_unique_objects(struct commit *commit)
@@ -907,6 +1057,7 @@ static int add_unique_objects(struct commit *commit)
int i, j, next;
char is_first = 1;
+ /* ...no, calculate unique objects */
strbuf_init(&os, 0);
strbuf_init(&ost, 0);
orig_buf = acc_buffer;
@@ -926,20 +1077,20 @@ static int add_unique_objects(struct commit *commit)
strbuf_setlen(acc_buffer, 0);
diff_tree_sha1(list->item->tree->object.sha1, commit->tree->object.sha1, "", &opts);
- qsort(acc_buffer->buf, acc_buffer->len / 21, 21, (int (*)(const void *, const void *))hashcmp);
+ qsort(acc_buffer->buf, acc_buffer->len / 20, 20, (int (*)(const void *, const void *))hashcmp);
/* take intersection */
if (!is_first) {
- for (next = i = j = 0; i < os.len; i += 21) {
+ for (next = i = j = 0; i < os.len; i += 20) {
while (j < ost.len && hashcmp((unsigned char *)(ost.buf + j), (unsigned char *)(os.buf + i)) < 0)
- j += 21;
+ j += 20;
if (j >= ost.len || hashcmp((unsigned char *)(ost.buf + j), (unsigned char *)(os.buf + i)))
continue;
if (next != i)
- memcpy(os.buf + next, os.buf + i, 21);
- next += 21;
+ memcpy(os.buf + next, os.buf + i, 20);
+ next += 20;
}
if (next != i)
@@ -948,25 +1099,102 @@ static int add_unique_objects(struct commit *commit)
is_first = 0;
}
+ /* no parents (!) */
if (is_first) {
acc_buffer = &os;
dump_tree(commit->tree, dump_tree_callback);
}
- if (os.len)
- qsort(os.buf, os.len / 21, 21, sort_type_hash);
-
+ /* the ordering of non-commit objects dosn't really matter, so we're not gonna bother */
acc_buffer = orig_buf;
- for (i = 0; i < os.len; i += 21)
- add_object_entry((unsigned char *)(os.buf + i), os.buf[i + 20] ? OBJ_TREE : OBJ_BLOB, 0, 0, 0);
+ for (i = 0; i < os.len; i += 20)
+ add_object_entry((unsigned char *)(os.buf + i), 0, 0, 0);
/* last but not least, the main tree */
- add_object_entry(commit->tree->object.sha1, OBJ_TREE, 0, 0, 0);
+ add_object_entry(commit->tree->object.sha1, 0, 0, 0);
+
+ return i / 20 + 1;
+}
+
+static int add_objects_verbatim_1(struct rev_cache_slice_map *mapping, int *index)
+{
+ unsigned char *map = mapping->map;
+ int i = *index, object_nr = 0;
+ struct rc_object_entry *entry = RC_OBTAIN_OBJECT_ENTRY(map + i);
+
+ i += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);
+ while (i < mapping->size) {
+ int pos = i;
- strbuf_release(&ost);
- strbuf_release(&os);
+ entry = RC_OBTAIN_OBJECT_ENTRY(map + i;
+ i += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);
+
+ if (entry->type == OBJ_COMMIT) {
+ *index = pos;
+ return object_nr;
+ }
- return i / 21 + 1;
+ strbuf_add(acc_buffer, map + pos, i - pos);
+ object_nr++;
+ }
+
+ *index = 0;
+ return object_nr;
+}
+
+static int add_objects_verbatim(struct rev_cache_info *rci, struct commit *commit)
+{
+ struct rev_cache_slice_map *map;
+ char found = 0;
+ struct rc_index_entry *ie;
+ struct rc_object_entry *entry;
+ int object_nr, i;
+
+ if (!rci->maps)
+ return -1;
+
+ /* check if we can continue where we left off */
+ map = rci->last_map;
+ if (!map)
+ goto search_me;
+
+ i = map->last_index;
+ entry = RC_OBTAIN_OBJECT_ENTRY(map->map + i);
+ if (hashcmp(entry->sha1, commit->object.sha1))
+ goto search_me;
+
+ found = 1;
+
+search_me:
+ if (!found) {
+ ie = search_index(commit->object.sha1);
+ if (!ie || ie->cache_index >= idx_head.cache_nr)
+ return -2;
+
+ map = rci->maps + ie->cache_index;
+ if (!map->size)
+ return -3;
+
+ i = ie->pos;
+ entry = RC_OBTAIN_OBJECT_ENTRY(map->map + i);
+ if (entry->type != OBJ_COMMIT || hashcmp(entry->sha1, commit->object.sha1))
+ return -4;
+ }
+
+ /* can't handle end commits */
+ if (entry->is_end)
+ return -5;
+
+ object_nr = add_objects_verbatim_1(map, &i);
+
+ /* remember this */
+ if (i) {
+ rci->last_map = map;
+ map->last_index = i;
+ } else
+ rci->last_map = 0;
+
+ return object_nr;
}
static void init_revcache_directory(void)
@@ -981,9 +1209,14 @@ static void init_revcache_directory(void)
void init_rev_cache_info(struct rev_cache_info *rci)
{
+ memset(rci, 0, sizeof(struct rev_cache_info));
+
rci->objects = 1;
rci->legs = 0;
rci->make_index = 1;
+ rci->fuse_me = 0;
+
+ rci->overwrite_all = 0;
rci->add_to_pending = 1;
@@ -1063,9 +1296,13 @@ int make_cache_slice(struct rev_cache_info *rci,
if (prepare_revision_walk(revs))
die("died preparing revision walk");
+ if (rci->legs)
+ make_legs(revs);
+
object_nr = total_sz = 0;
while ((commit = get_revision(revs)) != 0) {
struct rc_object_entry object;
+ int t;
strbuf_setlen(&merge_paths, 0);
strbuf_setlen(&split_paths, 0);
@@ -1091,12 +1328,17 @@ int make_cache_slice(struct rev_cache_info *rci,
commit->indegree = 0;
- add_object_entry(0, 0, &object, &merge_paths, &split_paths);
+ add_object_entry(0, &object, &merge_paths, &split_paths);
object_nr++;
- /* add all unique children for this commit */
- if (rci->objects && !object.is_end)
- object_nr += add_unique_objects(commit);
+ if (rci->objects && !object.is_end) {
+ if (rci->fuse_me && (t = add_objects_verbatim(rci, commit)) >= 0)
+ /* yay! we did it! */
+ object_nr += t;
+ else
+ /* add all unique children for this commit */
+ object_nr += add_unique_objects(commit);
+ }
/* print every ~1MB or so */
if (buffer.len > 1000000) {
@@ -1221,6 +1463,8 @@ int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,
unsigned char *map;
unsigned long max_date;
+ maybe_fill_with_defaults(rci);
+
if (!idx_map)
init_index();
@@ -1285,7 +1529,7 @@ int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,
} else
disked_entry = search_index_1(object_entry->sha1);
- if (disked_entry && !object_entry->is_start)
+ if (disked_entry && !object_entry->is_start && !rci->overwrite_all)
continue;
else if (disked_entry) {
/* mmm, pointer arithmetic... tasty */ /* (entry - idx_map = offset, so cast is valid) */
@@ -1339,8 +1583,7 @@ int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,
}
-/* add start-commits from each cache slice (uninterestingness will be propogated) */
-void starts_from_slices(struct rev_info *revs, unsigned int flags)
+void starts_from_slices(struct rev_info *revs, unsigned int flags, unsigned char *which, int n)
{
struct commit *commit;
int i;
@@ -1356,6 +1599,18 @@ void starts_from_slices(struct rev_info *revs, unsigned int flags)
if (!entry->is_start)
continue;
+ /* only include entries in 'which' slices */
+ if (n) {
+ int j;
+
+ for (j = 0; j < n; j++)
+ if (!hashcmp(idx_caches + entry->cache_index * 20, which + j * 20))
+ break;
+
+ if (j == n)
+ continue;
+ }
+
commit = lookup_commit(entry->sha1);
if (!commit)
continue;
@@ -1365,3 +1620,313 @@ void starts_from_slices(struct rev_info *revs, unsigned int flags)
}
}
+
+
+struct slice_fd_time {
+ unsigned char sha1[20];
+ int fd;
+ struct stat fi;
+};
+
+int slice_time_sort(const void *a, const void *b)
+{
+ unsigned long at, bt;
+
+ at = ((struct slice_fd_time *)a)->fi.st_ctime;
+ bt = ((struct slice_fd_time *)b)->fi.st_ctime;
+
+ if (at == bt)
+ return 0;
+
+ return at > bt ? 1 : -1;
+}
+
+int regenerate_cache_index(struct rev_cache_info *rci)
+{
+ DIR *dirh;
+ int i;
+ struct slice_fd_time info;
+ struct strbuf slices;
+
+ /* first remove old index if it exists */
+ unlink_or_warn(git_path("rev-cache/index"));
+
+ strbuf_init(&slices, 0);
+
+ dirh = opendir(git_path("rev-cache"));
+ if (dirh) {
+ struct dirent *de;
+ struct stat fi;
+ int fd;
+ unsigned char sha1[20];
+
+ while ((de = readdir(dirh))) {
+ if (de->d_name[0] == '.')
+ continue;
+
+ if (get_sha1_hex(de->d_name, sha1))
+ continue;
+
+ /* open with RDWR because of mmap call in make_cache_index() */
+ fd = open_cache_slice(sha1, O_RDONLY);
+ if (fd < 0 || fstat(fd, &fi)) {
+ warning("bad cache found [%s]; fuse recommended", de->d_name);
+ if (fd > 0)
+ close(fd);
+ continue;
+ }
+
+ hashcpy(info.sha1, sha1);
+ info.fd = fd;
+ memcpy(&info.fi, &fi, sizeof(struct stat));
+
+ strbuf_add(&slices, &info, sizeof(info));
+ }
+
+ closedir(dirh);
+ }
+
+ /* we want oldest first -> upon overlap, older slices are more likely to have a larger section,
+ * as of the overlapped commit */
+ qsort(slices.buf, slices.len / sizeof(info), sizeof(info), slice_time_sort);
+
+ for (i = 0; i < slices.len; i += sizeof(info)) {
+ struct slice_fd_time *infop = (struct slice_fd_time *)(slices.buf + i);
+ struct stat *fip = &infop->fi;
+ int fd = infop->fd;
+
+ if (make_cache_index(rci, infop->sha1, fd, fip->st_size) < 0)
+ die("error writing cache");
+
+ close(fd);
+ }
+
+ strbuf_release(&slices);
+
+ return 0;
+}
+
+static int add_slices_for_fuse(struct rev_cache_info *rci, struct string_list *files, struct strbuf *ignore)
+{
+ unsigned char sha1[20];
+ char base[PATH_MAX];
+ int baselen, i, slice_nr = 0;
+ struct stat fi;
+ DIR *dirh;
+ struct dirent *de;
+
+ strncpy(base, git_path("rev-cache"), sizeof(base));
+ baselen = strlen(base);
+
+ dirh = opendir(base);
+ if (!dirh)
+ return 0;
+
+ while ((de = readdir(dirh))) {
+ if (de->d_name[0] == '.')
+ continue;
+
+ base[baselen] = '/';
+ strncpy(base + baselen + 1, de->d_name, sizeof(base) - baselen - 1);
+
+ if (get_sha1_hex(de->d_name, sha1)) {
+ /* whatever it is, we don't need it... */
+ string_list_insert(base, files);
+ continue;
+ }
+
+ /* _theoretically_ it is possible a slice < ignore_size to map objects not covered by, yet reachable from,
+ * a slice >= ignore_size, meaning that we could potentially delete an 'unfused' slice; but if that
+ * ever *did* happen their cache structure'd be so fucked up they might as well refuse the entire thing.
+ * and at any rate the worst it'd do is make rev-list revert to standard walking in that (small) bit.
+ */
+ if (rci->ignore_size) {
+ if (stat(base, &fi))
+ warning("can't query file %s\n", base);
+ else if (fi.st_size >= rci->ignore_size) {
+ strbuf_add(ignore, sha1, 20);
+ continue;
+ }
+ } else {
+ /* check if a pointer */
+ struct cache_slice_pointer ptr;
+ int fd = open(base, O_RDONLY);
+
+ if (fd < 0)
+ goto dont_save;
+ if (sizeof(ptr) != read_in_full(fd, &ptr, sizeof(ptr)))
+ goto dont_save;
+
+ close(fd);
+ if (!strcmp(ptr.signature, "REVCOPTR")) {
+ strbuf_add(ignore, sha1, 20);
+ continue;
+ }
+ }
+
+dont_save:
+ for (i = idx_head.cache_nr - 1; i >= 0; i--) {
+ if (!hashcmp(idx_caches + i * 20, sha1))
+ break;
+ }
+
+ if (i >= 0)
+ rci->maps[i].size = 1;
+
+ string_list_insert(base, files);
+ slice_nr++;
+ }
+
+ closedir(dirh);
+
+ return slice_nr;
+}
+
+/* the most work-intensive attributes in the cache are the unique objects and size, both
+ * of which can be re-used. although path structures will be isomorphic, path generation is
+ * not particularly expensive, and at any rate we need to re-sort the commits */
+int fuse_cache_slices(struct rev_cache_info *rci, struct rev_info *revs)
+{
+ unsigned char cache_sha1[20];
+ struct string_list files = {0, 0, 0, 1}; /* dup */
+ struct strbuf ignore;
+ int i;
+
+ maybe_fill_with_defaults(rci);
+
+ if (!idx_map)
+ init_index();
+ if (!idx_map)
+ return -1;
+
+ strbuf_init(&ignore, 0);
+ rci->maps = xcalloc(idx_head.cache_nr, sizeof(struct rev_cache_slice_map));
+ if (add_slices_for_fuse(rci, &files, &ignore) <= 1) {
+ printf("nothing to fuse\n");
+ return 1;
+ }
+
+ if (ignore.len) {
+ starts_from_slices(revs, UNINTERESTING, (unsigned char *)ignore.buf, ignore.len / 20);
+ strbuf_release(&ignore);
+ }
+
+ /* initialize mappings */
+ for (i = idx_head.cache_nr - 1; i >= 0; i--) {
+ struct rev_cache_slice_map *map = rci->maps + i;
+ struct stat fi;
+ int fd;
+
+ if (!map->size)
+ continue;
+ map->size = 0;
+
+ /* pointers are never fused, so we can use open directly */
+ fd = open(git_path("rev-cache/%s", sha1_to_hex(idx_caches + i * 20)), O_RDONLY);
+ if (fd <= 0 || fstat(fd, &fi))
+ continue;
+ if (fi.st_size < sizeof(struct rc_slice_header))
+ continue;
+
+ map->map = xmmap(0, fi.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (map->map == MAP_FAILED)
+ continue;
+
+ close(fd);
+ map->size = fi.st_size;
+ }
+
+ rci->make_index = 0;
+ rci->fuse_me = 1;
+ if (make_cache_slice(rci, revs, 0, 0, cache_sha1) < 0)
+ die("can't make cache slice");
+
+ printf("%s\n", sha1_to_hex(cache_sha1));
+
+ /* clean up time! */
+ for (i = idx_head.cache_nr - 1; i >= 0; i--) {
+ struct rev_cache_slice_map *map = rci->maps + i;
+
+ if (!map->size)
+ continue;
+
+ munmap(map->map, map->size);
+ }
+ free(rci->maps);
+ cleanup_cache_slices();
+
+ for (i = 0; i < files.nr; i++) {
+ char *name = files.items[i].string;
+
+ fprintf(stderr, "removing %s\n", name);
+ unlink_or_warn(name);
+ }
+
+ string_list_clear(&files, 0);
+
+ return regenerate_cache_index(rci);
+}
+
+static int verify_cache_slice(const char *slice_path, unsigned char *sha1)
+{
+ struct rc_slice_header head;
+ int fd, len, retval = -1;
+ unsigned char *map = MAP_FAILED;
+ struct stat fi;
+
+ len = strlen(slice_path);
+ if (len < 40)
+ return -2;
+ if (get_sha1_hex(slice_path + len - 40, sha1))
+ return -3;
+
+ fd = open(slice_path, O_RDONLY);
+ if (fd == -1)
+ goto end;
+ if (fstat(fd, &fi) || fi.st_size < sizeof(head))
+ goto end;
+
+ map = xmmap(0, sizeof(head), PROT_READ, MAP_PRIVATE, fd, 0);
+ if (map == MAP_FAILED)
+ goto end;
+ if (get_cache_slice_header(sha1, map, fi.st_size, &head))
+ goto end;
+
+ retval = 0;
+
+end:
+ if (map != MAP_FAILED)
+ munmap(map, sizeof(head));
+ if (fd > 0)
+ close(fd);
+
+ return retval;
+}
+
+int make_cache_slice_pointer(struct rev_cache_info *rci, const char *slice_path)
+{
+ struct cache_slice_pointer ptr;
+ int fd;
+ unsigned char sha1[20];
+
+ maybe_fill_with_defaults(rci);
+ rci->overwrite_all = 1;
+
+ if (verify_cache_slice(slice_path, sha1) < 0)
+ return -1;
+
+ strcpy(ptr.signature, "REVCOPTR");
+ ptr.version = SUPPORTED_REVCOPTR_VERSION;
+ strcpy(ptr.path, make_nonrelative_path(slice_path));
+
+ fd = open(git_path("rev-cache/%s", sha1_to_hex(sha1)), O_RDWR | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0)
+ return -2;
+
+ write_in_full(fd, &ptr, sizeof(ptr));
+ make_cache_index(rci, sha1, fd, sizeof(ptr));
+
+ close(fd);
+
+ return 0;
+}
diff --git a/rev-cache.h b/rev-cache.h
index a471fbf..14437d8 100644
--- a/rev-cache.h
+++ b/rev-cache.h
@@ -3,6 +3,7 @@
#define SUPPORTED_REVCACHE_VERSION 1
#define SUPPORTED_REVINDEX_VERSION 1
+#define SUPPORTED_REVCOPTR_VERSION 1
#define RC_PATH_SIZE(x) (sizeof(uint16_t) * (x))
@@ -10,6 +11,7 @@
#define RC_OBTAIN_INDEX_ENTRY(p) from_disked_rc_index_entry((struct rc_index_entry_ondisk *)(p), 0)
#define RC_ACTUAL_OBJECT_ENTRY_SIZE(e) (sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE((e)->merge_nr + (e)->split_nr) + (e)->size_size)
+#define RC_ENTRY_SIZE_OFFSET(e) (RC_ACTUAL_OBJECT_ENTRY_SIZE(e) - (e)->size_size)
/* single index maps objects to cache files */
struct rc_index_header {
@@ -90,6 +92,7 @@ struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondis
struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry *src, struct rc_object_entry_ondisk *dst);
extern unsigned char *get_cache_slice(struct commit *commit);
+extern int open_cache_slice(unsigned char *sha1, int flags);
extern int traverse_cache_slice(struct rev_info *revs,
unsigned char *cache_sha1, struct commit *commit,
unsigned long *date_so_far, int *slop_so_far,
@@ -102,6 +105,10 @@ extern int make_cache_slice(struct rev_cache_info *rci,
extern int make_cache_index(struct rev_cache_info *rci, unsigned char *cache_sha1,
int fd, unsigned int size);
-extern void starts_from_slices(struct rev_info *revs, unsigned int flags);
+extern void starts_from_slices(struct rev_info *revs, unsigned int flags, unsigned char *which, int n);
+extern int fuse_cache_slices(struct rev_cache_info *rci, struct rev_info *revs);
+extern int regenerate_cache_index(struct rev_cache_info *rci);
+extern int make_cache_slice_pointer(struct rev_cache_info *rci, const char *slice_path);
#endif
+
diff --git a/revision.h b/revision.h
index e767a43..c3ec1b3 100644
--- a/revision.h
+++ b/revision.h
@@ -19,17 +19,31 @@
struct rev_info;
struct log_info;
+struct rev_cache_slice_map {
+ unsigned char *map;
+ int size;
+ int last_index;
+};
+
struct rev_cache_info {
/* generation flags */
unsigned objects : 1,
legs : 1,
- make_index : 1;
+ make_index : 1,
+ fuse_me : 1;
+
+ /* index inclusion */
+ unsigned overwrite_all : 1;
/* traversal flags */
unsigned add_to_pending : 1;
/* fuse options */
unsigned int ignore_size;
+
+ /* reserved */
+ struct rev_cache_slice_map *maps,
+ *last_map;
};
struct rev_info {
--
tg: (e07ed0b..) t/revcache/misc (depends on: t/revcache/objects)
^ permalink raw reply related
* Re: [PATCH 5/6 (v2)] full integration of rev-cache into git's revision walker, completed test suite
From: Nick Edelen @ 2009-08-16 22:42 UTC (permalink / raw)
To: Nick Edelen, Junio C Hamano, Nicolas Pitre, Johannes Schindelin,
Sam Vilain
In-Reply-To: <op.uyb1vl2jtdk399@sirnot.private>
This patch provides a working integration of rev-cache into the revision
walker, along with some touch-ups:
- integration into revision walker and list-objects
- tweak of object generation to take advantage of the 'unique' field
- more fluid handling of damaged cache slices
- numerous tests for both features from the previous patch, and the
integration's integrity
'Integration' is rather broad -- a more detailed description follows for each
aspect:
- rev-cache
the traversal mechanism is updated to handle many of the non-prune options
rev-list does (date limiting, slop-handling, etc.), and is adjusted to allow
for non-fatal cache-traversal failures.
- revision walker
both limited and unlimited traversal attempt to use the cache when possible,
smoothly falling back if it's not.
- list-objects
object listing does not recurse into cached trees, and has been adjusted to
guarantee commit-tag-tree-blob ordering.
Signed-off-by: Nick Edelen <sirnot@gmail.com>
---
builtin-rev-cache.c | 40 ++++++++
list-objects.c | 49 ++++++++--
rev-cache.c | 228 +++++++++++++++++++++++++++++++++++++++-----
revision.c | 88 ++++++++++++++---
t/t6015-rev-cache-list.sh | 151 ++++++++++++++++++++++++++++-
5 files changed, 501 insertions(+), 55 deletions(-)
diff --git a/builtin-rev-cache.c b/builtin-rev-cache.c
index 7ca39c4..a3489ce 100644
--- a/builtin-rev-cache.c
+++ b/builtin-rev-cache.c
@@ -4,6 +4,7 @@
#include "diff.h"
#include "revision.h"
#include "rev-cache.h"
+#include "list-objects.h"
unsigned long default_ignore_size = 50 * 1024 * 1024; /* 50mb */
@@ -78,6 +79,43 @@ static int handle_add(int argc, const char *argv[]) /* args beyond this command
return 0;
}
+static void show_commit(struct commit *commit, void *data)
+{
+ printf("%s\n", sha1_to_hex(commit->object.sha1));
+}
+
+static void show_object(struct object *obj, const struct name_path *path, const char *last)
+{
+ printf("%s\n", sha1_to_hex(obj->sha1));
+}
+
+static int test_rev_list(int argc, const char *argv[])
+{
+ struct rev_info revs;
+ unsigned int flags = 0;
+ int i;
+
+ init_revisions(&revs, 0);
+
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "--not"))
+ flags ^= UNINTERESTING;
+ else if (!strcmp(argv[i], "--objects"))
+ revs.tree_objects = revs.blob_objects = 1;
+ else
+ handle_revision_arg(argv[i], &revs, flags, 1);
+ }
+
+ setup_revisions(0, 0, &revs, 0);
+ revs.topo_order = 1;
+ revs.lifo = 1;
+ prepare_revision_walk(&revs);
+
+ traverse_commit_list(&revs, show_commit, show_object, 0);
+
+ return 0;
+}
+
static int handle_walk(int argc, const char *argv[])
{
struct commit *commit;
@@ -270,6 +308,8 @@ int cmd_rev_cache(int argc, const char *argv[], const char *prefix)
r = handle_walk(argc, argv);
else if (!strcmp(arg, "index"))
r = handle_index(argc, argv);
+ else if (!strcmp(arg, "test"))
+ r = test_rev_list(argc, argv);
else if (!strcmp(arg, "alt"))
r = handle_alt(argc, argv);
else
diff --git a/list-objects.c b/list-objects.c
index 8953548..821a290 100644
--- a/list-objects.c
+++ b/list-objects.c
@@ -74,22 +74,33 @@ static void process_tree(struct rev_info *revs,
die("bad tree object");
if (obj->flags & (UNINTERESTING | SEEN))
return;
- if (parse_tree(tree) < 0)
- die("bad tree object %s", sha1_to_hex(obj->sha1));
+
obj->flags |= SEEN;
show(obj, path, name);
+ if (obj->flags & FACE_VALUE)
+ return;
+
+ /* traverse_commit_list is only used for enumeration purposes,
+ * ie. nothing relies on trees being parsed in this routine */
+ if (parse_tree(tree) < 0)
+ die("bad tree object %s", sha1_to_hex(obj->sha1));
+
me.up = path;
me.elem = name;
me.elem_len = strlen(name);
-
init_tree_desc(&desc, tree->buffer, tree->size);
while (tree_entry(&desc, &entry)) {
- if (S_ISDIR(entry.mode))
+ if (S_ISDIR(entry.mode)) {
+ struct tree *subtree = lookup_tree(entry.sha1);
+ if (!subtree)
+ continue;
+
+ subtree->object.flags &= ~FACE_VALUE;
process_tree(revs,
- lookup_tree(entry.sha1),
+ subtree,
show, &me, entry.path);
- else if (S_ISGITLINK(entry.mode))
+ } else if (S_ISGITLINK(entry.mode))
process_gitlink(revs, entry.sha1,
show, &me, entry.path);
else
@@ -136,6 +147,7 @@ void mark_edges_uninteresting(struct commit_list *list,
static void add_pending_tree(struct rev_info *revs, struct tree *tree)
{
+ tree->object.flags &= ~FACE_VALUE;
add_pending_object(revs, &tree->object, "");
}
@@ -146,17 +158,27 @@ void traverse_commit_list(struct rev_info *revs,
{
int i;
struct commit *commit;
+ enum object_type what = OBJ_TAG;
+ char face_value = 0;
while ((commit = get_revision(revs)) != NULL) {
- add_pending_tree(revs, commit->tree);
+ if (!(commit->object.flags & FACE_VALUE))
+ add_pending_tree(revs, commit->tree);
+ else
+ face_value = 1;
show_commit(commit, data);
}
+
+loop_objects:
for (i = 0; i < revs->pending.nr; i++) {
struct object_array_entry *pending = revs->pending.objects + i;
struct object *obj = pending->item;
const char *name = pending->name;
if (obj->flags & (UNINTERESTING | SEEN))
continue;
+ if (obj->type != what && face_value)
+ continue;
+
if (obj->type == OBJ_TAG) {
obj->flags |= SEEN;
show_object(obj, NULL, name);
@@ -175,6 +197,19 @@ void traverse_commit_list(struct rev_info *revs,
die("unknown pending object %s (%s)",
sha1_to_hex(obj->sha1), name);
}
+ if (face_value) {
+ switch (what) {
+ case OBJ_TAG :
+ what = OBJ_TREE;
+ goto loop_objects;
+ case OBJ_TREE :
+ what = OBJ_BLOB;
+ goto loop_objects;
+ default :
+ break;
+ }
+ }
+
if (revs->pending.nr) {
free(revs->pending.objects);
revs->pending.nr = 0;
diff --git a/rev-cache.c b/rev-cache.c
index 8eac1f0..7eefd3c 100644
--- a/rev-cache.c
+++ b/rev-cache.c
@@ -11,6 +11,12 @@
#include "run-command.h"
#include "string-list.h"
+
+struct bad_slice {
+ unsigned char sha1[20];
+ struct bad_slice *next;
+};
+
struct cache_slice_pointer {
char signature[8]; /* REVCOPTR */
char version;
@@ -23,8 +29,9 @@ static uint32_t fanout[0xff + 2];
static unsigned char *idx_map;
static int idx_size;
static struct rc_index_header idx_head;
+static char no_idx, add_to_pending;
+static struct bad_slice *bad_slices;
static unsigned char *idx_caches;
-static char no_idx;
static struct strbuf *acc_buffer;
@@ -119,6 +126,30 @@ struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry
return dst;
}
+static void mark_bad_slice(unsigned char *sha1)
+{
+ struct bad_slice *bad;
+
+ bad = xcalloc(sizeof(struct bad_slice), 1);
+ hashcpy(bad->sha1, sha1);
+
+ bad->next = bad_slices;
+ bad_slices = bad;
+}
+
+static int is_bad_slice(unsigned char *sha1)
+{
+ struct bad_slice *bad = bad_slices;
+
+ while (bad) {
+ if (!hashcmp(bad->sha1, sha1))
+ return 1;
+ bad = bad->next;
+ }
+
+ return 0;
+}
+
static int get_index_head(unsigned char *map, int len, struct rc_index_header *head, uint32_t *fanout, unsigned char **caches)
{
struct rc_index_header whead;
@@ -244,6 +275,7 @@ static struct rc_index_entry *search_index(unsigned char *sha1)
unsigned char *get_cache_slice(struct commit *commit)
{
struct rc_index_entry *ie;
+ unsigned char *sha1;
if (!idx_map) {
if (no_idx)
@@ -255,8 +287,13 @@ unsigned char *get_cache_slice(struct commit *commit)
return 0;
ie = search_index(commit->object.sha1);
- if (ie && ie->cache_index < idx_head.cache_nr)
- return idx_caches + ie->cache_index * 20;
+ if (ie && ie->cache_index < idx_head.cache_nr) {
+ sha1 = idx_caches + ie->cache_index * 20;
+
+ if (is_bad_slice(sha1))
+ return 0;
+ return sha1;
+ }
return 0;
}
@@ -266,6 +303,20 @@ unsigned char *get_cache_slice(struct commit *commit)
static unsigned long decode_size(unsigned char *str, int len);
+/* on failure */
+static void restore_commit(struct commit *commit)
+{
+ commit->object.flags &= ~(ADDED | SEEN | FACE_VALUE);
+
+ if (!commit->object.parsed) {
+ while (pop_commit(&commit->parents))
+ ;
+
+ parse_commit(commit);
+ }
+
+}
+
static void handle_noncommit(struct rev_info *revs, struct commit *commit, unsigned char *ptr, struct rc_object_entry *entry)
{
struct blob *blob;
@@ -305,23 +356,27 @@ static void handle_noncommit(struct rev_info *revs, struct commit *commit, unsig
}
obj->flags |= FACE_VALUE;
- add_pending_object(revs, obj, "");
+ if (add_to_pending)
+ add_pending_object(revs, obj, "");
}
-static int setup_traversal(struct rc_slice_header *head, unsigned char *map, struct commit *commit, struct commit_list **work)
+static int setup_traversal(struct rc_slice_header *head, unsigned char *map, struct commit *commit, struct commit_list **work,
+ struct commit_list **unwork, int *ipath_nr, int *upath_nr, char *ioutside)
{
struct rc_index_entry *iep;
struct rc_object_entry *oep;
struct commit_list *prev, *wp, **wpp;
int retval;
- iep = search_index(commit->object.sha1), 0;
+ iep = search_index(commit->object.sha1);
oep = RC_OBTAIN_OBJECT_ENTRY(map + iep->pos);
+ if (commit->object.flags & UNINTERESTING) {
+ ++*upath_nr;
+ oep->uninteresting = 1;
+ } else
+ ++*ipath_nr;
- /* the .uniniteresting bit isn't strictly necessary, as we check the object during traversal as well,
- * but we might as well initialize it while we're at it */
oep->include = 1;
- oep->uninteresting = !!(commit->object.flags & UNINTERESTING);
to_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));
retval = iep->pos;
@@ -336,6 +391,10 @@ static int setup_traversal(struct rc_slice_header *head, unsigned char *map, str
/* is this in our cache slice? */
iep = search_index(obj->sha1);
if (!iep || hashcmp(idx_caches + iep->cache_index * 20, head->sha1)) {
+ /* there are interesing objects outside the slice */
+ if (!(obj->flags & UNINTERESTING))
+ *ioutside = 1;
+
prev = wp;
wp = wp->next;
wpp = ℘
@@ -352,11 +411,20 @@ static int setup_traversal(struct rc_slice_header *head, unsigned char *map, str
oep->uninteresting = !!(obj->flags & UNINTERESTING);
to_disked_rc_object_entry(oep, (struct rc_object_entry_ondisk *)(map + iep->pos));
+ /* count even if not in slice so we can stop enumerating if possible */
+ if (obj->flags & UNINTERESTING)
+ ++*upath_nr;
+ else
+ ++*ipath_nr;
+
/* remove from work list */
co = pop_commit(wpp);
wp = *wpp;
if (prev)
prev->next = wp;
+
+ /* ...and store in temp list so we can restore work on failure */
+ commit_list_insert(co, unwork);
}
return retval;
@@ -373,13 +441,18 @@ static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *m
unsigned long *date_so_far, int *slop_so_far,
struct commit_list ***queue, struct commit_list **work)
{
- struct commit_list *insert_cache = 0;
+ struct commit_list *insert_cache = 0, *myq = 0, **myqp = &myq, *mywork = 0, **myworkp = &mywork, *unwork = 0;
struct commit **last_objects, *co;
- int i, total_path_nr = head->path_nr, retval = -1;
- char consume_children = 0;
+ unsigned long date = date_so_far ? *date_so_far : ~0ul;
+ int i, ipath_nr = 0, upath_nr = 0, orig_obj_nr = 0,
+ total_path_nr = head->path_nr, retval = -1, slop = slop_so_far ? *slop_so_far : SLOP;
+ char consume_children = 0, ioutside = 0;
unsigned char *paths;
- i = setup_traversal(head, map, commit, work);
+ /* take note in case we need to regress */
+ orig_obj_nr = revs->pending.nr;
+
+ i = setup_traversal(head, map, commit, work, &unwork, &ipath_nr, &upath_nr, &ioutside);
if (i < 0)
return -1;
@@ -427,6 +500,7 @@ static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *m
if ((paths[path] & IPATH) && (paths[path] & UPATH)) {
paths[path] = UPATH;
+ ipath_nr--;
/* mark edge */
if (last_objects[path]) {
@@ -437,6 +511,7 @@ static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *m
last_objects[path]->object.flags &= ~FACE_VALUE;
last_objects[path] = 0;
}
+ obj->flags |= BOUNDARY;
}
/* now we gotta re-assess the whole interesting thing... */
@@ -460,8 +535,10 @@ static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *m
last_objects[p]->object.flags &= ~FACE_VALUE;
last_objects[p] = 0;
}
- } else if (last_objects[p] && !last_objects[p]->object.parsed)
+ obj->flags |= BOUNDARY;
+ } else if (last_objects[p] && !last_objects[p]->object.parsed) {
commit_list_insert(co, &last_objects[p]->parents);
+ }
/* can't close a merge path until all are parents have been encountered */
if (GET_COUNT(paths[p])) {
@@ -471,14 +548,33 @@ static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *m
continue;
}
+ if (paths[p] & IPATH)
+ ipath_nr--;
+ else
+ upath_nr--;
+
paths[p] = 0;
last_objects[p] = 0;
}
}
/* make topo relations */
- if (last_objects[path] && !last_objects[path]->object.parsed)
+ if (last_objects[path] && !last_objects[path]->object.parsed) {
commit_list_insert(co, &last_objects[path]->parents);
+ }
+
+ /* we've been here already */
+ if (obj->flags & ADDED) {
+ if (entry->uninteresting && !(obj->flags & UNINTERESTING)) {
+ obj->flags |= UNINTERESTING;
+ mark_parents_uninteresting(co);
+ upath_nr--;
+ } else if (!entry->uninteresting)
+ ipath_nr--;
+
+ paths[path] = 0;
+ continue;
+ }
/* initialize commit */
if (!entry->is_end) {
@@ -491,24 +587,51 @@ static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *m
if (entry->uninteresting)
obj->flags |= UNINTERESTING;
+ else if (co->date < date)
+ date = co->date;
/* we need to know what the edges are */
last_objects[path] = co;
/* add to list */
- if (!(obj->flags & UNINTERESTING) || revs->show_all) {
- if (entry->is_end)
- insert_by_date_cached(co, work, insert_cache, &insert_cache);
- else
- *queue = &commit_list_insert(co, *queue)->next;
+ if (slop && !(revs->min_age != -1 && co->date > revs->min_age)) {
+
+ if (!(obj->flags & UNINTERESTING) || revs->show_all) {
+ if (entry->is_end)
+ myworkp = &commit_list_insert(co, myworkp)->next;
+ else
+ myqp = &commit_list_insert(co, myqp)->next;
+
+ /* add children to list as well */
+ if (obj->flags & UNINTERESTING)
+ consume_children = 0;
+ else
+ consume_children = 1;
+ }
- /* add children to list as well */
- if (obj->flags & UNINTERESTING)
- consume_children = 0;
- else
- consume_children = 1;
}
+ /* should we continue? */
+ if (!slop) {
+ if (!upath_nr) {
+ break;
+ } else if (ioutside || revs->show_all) {
+ /* pass it back to rev-list
+ * we purposely ignore everything outside this cache, so we don't needlessly traverse the whole
+ * thing on uninteresting, but that does mean that we may need to bounce back
+ * and forth a few times with rev-list */
+ myworkp = &commit_list_insert(co, myworkp)->next;
+
+ paths[path] = 0;
+ upath_nr--;
+ } else {
+ break;
+ }
+ } else if (!ipath_nr && co->date <= date)
+ slop--;
+ else
+ slop = SLOP;
+
/* open parents */
if (entry->merge_nr) {
int j, off = index + sizeof(struct rc_object_entry_ondisk);
@@ -523,6 +646,11 @@ static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *m
if (paths[p] & flag)
continue;
+ if (flag == IPATH)
+ ipath_nr++;
+ else
+ upath_nr++;
+
paths[p] |= flag;
}
@@ -532,12 +660,55 @@ static int traverse_cache_slice_1(struct rc_slice_header *head, unsigned char *m
}
+ if (date_so_far)
+ *date_so_far = date;
+ if (slop_so_far)
+ *slop_so_far = slop;
retval = 0;
+ /* success: attach to given lists */
+ if (myqp != &myq) {
+ **queue = myq;
+ *queue = myqp;
+ }
+
+ while ((co = pop_commit(&mywork)) != 0) {
+ insert_by_date_cached(co, work, insert_cache, &insert_cache);
+ }
+
+ /* free backup */
+ while (pop_commit(&unwork))
+ ;
+
end:
free(paths);
free(last_objects);
+ /* failure: restore work to previous condition
+ * (cache corruption should *not* be fatal) */
+ if (retval) {
+ while ((co = pop_commit(&unwork)) != 0) {
+ restore_commit(co);
+ co->object.flags |= SEEN;
+ insert_by_date(co, work);
+ }
+
+ /* free lists */
+ while ((co = pop_commit(&myq)) != 0)
+ restore_commit(co);
+
+ while ((co = pop_commit(&mywork)) != 0)
+ restore_commit(co);
+
+ /* truncate object array */
+ for (i = orig_obj_nr; i < revs->pending.nr; i++) {
+ struct object *obj = revs->pending.objects[i].item;
+
+ obj->flags &= ~FACE_VALUE;
+ }
+ revs->pending.nr = orig_obj_nr;
+ }
+
return retval;
}
@@ -628,6 +799,7 @@ int traverse_cache_slice(struct rev_info *revs,
/* load options */
rci = &revs->rev_cache_info;
+ add_to_pending = rci->add_to_pending;
memset(&head, 0, sizeof(struct rc_slice_header));
@@ -651,6 +823,10 @@ end:
if (fd != -1)
close(fd);
+ /* remember this! */
+ if (retval)
+ mark_bad_slice(cache_sha1);
+
return retval;
}
diff --git a/revision.c b/revision.c
index 485bf72..4640536 100644
--- a/revision.c
+++ b/revision.c
@@ -12,6 +12,7 @@
#include "patch-ids.h"
#include "decorate.h"
#include "log-tree.h"
+#include "rev-cache.h"
volatile show_early_output_fn_t show_early_output;
@@ -638,6 +639,8 @@ static int limit_list(struct rev_info *revs)
struct commit_list *list = revs->commits;
struct commit_list *newlist = NULL;
struct commit_list **p = &newlist;
+ unsigned char *cache_sha1;
+ char used_cache;
while (list) {
struct commit_list *entry = list;
@@ -650,24 +653,39 @@ static int limit_list(struct rev_info *revs)
if (revs->max_age != -1 && (commit->date < revs->max_age))
obj->flags |= UNINTERESTING;
- if (add_parents_to_list(revs, commit, &list, NULL) < 0)
- return -1;
- if (obj->flags & UNINTERESTING) {
- mark_parents_uninteresting(commit);
- if (revs->show_all)
- p = &commit_list_insert(commit, p)->next;
- slop = still_interesting(list, date, slop);
- if (slop)
+
+ /* rev-cache to the rescue!!! */
+ used_cache = 0;
+ if (!revs->dont_cache_me && !(obj->flags & ADDED)) {
+ cache_sha1 = get_cache_slice(commit);
+ if (cache_sha1) {
+ if (traverse_cache_slice(revs, cache_sha1, commit, &date, &slop, &p, &list) < 0)
+ used_cache = 0;
+ else
+ used_cache = 1;
+ }
+ }
+
+ if (!used_cache) {
+ if (add_parents_to_list(revs, commit, &list, NULL) < 0)
+ return -1;
+ if (obj->flags & UNINTERESTING) {
+ mark_parents_uninteresting(commit); /* ME: why? */
+ if (revs->show_all)
+ p = &commit_list_insert(commit, p)->next;
+ slop = still_interesting(list, date, slop);
+ if (slop > 0)
+ continue;
+ /* If showing all, add the whole pending list to the end */
+ if (revs->show_all)
+ *p = list;
+ break;
+ }
+ if (revs->min_age != -1 && (commit->date > revs->min_age))
continue;
- /* If showing all, add the whole pending list to the end */
- if (revs->show_all)
- *p = list;
- break;
+ date = commit->date;
+ p = &commit_list_insert(commit, p)->next;
}
- if (revs->min_age != -1 && (commit->date > revs->min_age))
- continue;
- date = commit->date;
- p = &commit_list_insert(commit, p)->next;
show = show_early_output;
if (!show)
@@ -813,6 +831,8 @@ void init_revisions(struct rev_info *revs, const char *prefix)
revs->diffopt.prefix = prefix;
revs->diffopt.prefix_length = strlen(prefix);
}
+
+ init_rev_cache_info(&revs->rev_cache_info);
}
static void add_pending_commit_list(struct rev_info *revs,
@@ -1372,6 +1392,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
if (revs->reflog_info && revs->graph)
die("cannot combine --walk-reflogs with --graph");
+ /* limits on caching
+ * todo: implement this functionality */
+ if (revs->prune || revs->diff)
+ revs->dont_cache_me = 1;
+
return left;
}
@@ -1654,6 +1679,8 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
{
if (!opt->grep_filter.pattern_list)
return 1;
+ if (!commit->object.parsed)
+ parse_commit(commit);
return grep_buffer(&opt->grep_filter,
NULL, /* we say nothing, not even filename */
commit->buffer, strlen(commit->buffer));
@@ -1706,6 +1733,7 @@ static struct commit *get_revision_1(struct rev_info *revs)
do {
struct commit_list *entry = revs->commits;
struct commit *commit = entry->item;
+ struct object *obj = &commit->object;
revs->commits = entry->next;
free(entry);
@@ -1722,11 +1750,39 @@ static struct commit *get_revision_1(struct rev_info *revs)
if (revs->max_age != -1 &&
(commit->date < revs->max_age))
continue;
+
+ if (!revs->dont_cache_me) {
+ struct commit_list *queue = 0, **queuep = &queue;
+ unsigned char *cache_sha1;
+
+ if (obj->flags & ADDED)
+ goto skip_parenting;
+
+ cache_sha1 = get_cache_slice(commit);
+ if (cache_sha1) {
+ if (!traverse_cache_slice(revs, cache_sha1, commit, 0, 0, &queuep, &revs->commits)) {
+ struct commit_list *work = revs->commits;
+
+ /* attach queue to end of ->commits */
+ while (work && work->next)
+ work = work->next;
+
+ if (work)
+ work->next = queue;
+ else
+ revs->commits = queue;
+
+ goto skip_parenting;
+ }
+ }
+ }
+
if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0)
die("Failed to traverse parents of commit %s",
sha1_to_hex(commit->object.sha1));
}
+skip_parenting:
switch (simplify_commit(revs, commit)) {
case commit_ignore:
continue;
diff --git a/t/t6015-rev-cache-list.sh b/t/t6015-rev-cache-list.sh
index afa0303..fa6df21 100755
--- a/t/t6015-rev-cache-list.sh
+++ b/t/t6015-rev-cache-list.sh
@@ -38,6 +38,7 @@ test_expect_success 'init repo' '
git add . &&
git commit -m "omg" &&
+ sleep 2 &&
git branch b4 &&
git checkout b4 &&
echo shazam >file8 &&
@@ -46,7 +47,7 @@ test_expect_success 'init repo' '
git merge -m "merge b2" b2 &&
echo bam >smoke/pipe &&
- git add .
+ git add . &&
git commit -m "bam" &&
git checkout master &&
@@ -71,18 +72,26 @@ test_expect_success 'init repo' '
git add . &&
git commit -m "lol" &&
+ sleep 2 &&
git checkout master &&
git merge -m "triple merge" b1 b11 &&
git rm -r d1 &&
+ sleep 2 &&
git commit -a -m "oh noes"
'
-git-rev-list HEAD --not HEAD~3 >proper_commit_list_limited
-git-rev-list HEAD >proper_commit_list
-git-rev-list HEAD --objects >proper_object_list
+max_date=`git-rev-list --timestamp HEAD~1 --max-count=1 | grep -e "^[0-9]*" -o`
+min_date=`git-rev-list --timestamp b4 --max-count=1 | grep -e "^[0-9]*" -o`
+
+git-rev-list --topo-order HEAD --not HEAD~3 >proper_commit_list_limited
+git-rev-list --topo-order HEAD --not HEAD~2 >proper_commit_list_limited2
+git-rev-list --topo-order HEAD >proper_commit_list
+git-rev-list --objects HEAD >proper_object_list
+git-rev-list HEAD --max-age=$min_date --min-age=$max_date >proper_list_date_limited
+
+cache_sha1=`git-rev-cache add HEAD 2>output.err`
test_expect_success 'make cache slice' '
- git-rev-cache add HEAD 2>output.err &&
grep "final return value: 0" output.err
'
@@ -102,11 +111,141 @@ test_expect_success 'test rev-caches walker directly (unlimited)' '
test_cmp_sorted list proper_commit_list
'
+test_expect_success 'test rev-list traversal (limited)' '
+ git-rev-list HEAD --not HEAD~3 >list &&
+ test_cmp list proper_commit_list_limited
+'
+
+test_expect_success 'test rev-list traversal (unlimited)' '
+ git-rev-list HEAD >list &&
+ test_cmp list proper_commit_list
+'
+
#do the same for objects
test_expect_success 'test rev-caches walker with objects' '
git-rev-cache walk --objects HEAD >list &&
test_cmp_sorted list proper_object_list
'
-test_done
+test_expect_success 'test rev-list with objects (topo order)' '
+ git-rev-list --topo-order --objects HEAD >list &&
+ test_cmp_sorted list proper_object_list
+'
+
+test_expect_success 'test rev-list with objects (no order)' '
+ git-rev-list --objects HEAD >list &&
+ test_cmp_sorted list proper_object_list
+'
+
+#verify age limiting
+test_expect_success 'test rev-list date limiting (topo order)' '
+ git-rev-list --topo-order --max-age=$min_date --min-age=$max_date HEAD >list &&
+ test_cmp_sorted list proper_list_date_limited
+'
+
+test_expect_success 'test rev-list date limiting (no order)' '
+ git-rev-list --max-age=$min_date --min-age=$max_date HEAD >list &&
+ test_cmp_sorted list proper_list_date_limited
+'
+
+#check partial cache slice
+test_expect_success 'saving old cache and generating partial slice' '
+ cp ".git/rev-cache/$cache_sha1" .git/rev-cache/.old &&
+ rm ".git/rev-cache/$cache_sha1" .git/rev-cache/index &&
+
+ git-rev-cache add HEAD~2 2>output.err &&
+ grep "final return value: 0" output.err
+'
+
+test_expect_success 'rev-list with wholly interesting partial slice' '
+ git-rev-list --topo-order HEAD >list &&
+ test_cmp list proper_commit_list
+'
+
+test_expect_success 'rev-list with partly uninteresting partial slice' '
+ git-rev-list --topo-order HEAD --not HEAD~3 >list &&
+ test_cmp list proper_commit_list_limited
+'
+
+test_expect_success 'rev-list with wholly uninteresting partial slice' '
+ git-rev-list --topo-order HEAD --not HEAD~2 >list &&
+ test_cmp list proper_commit_list_limited2
+'
+
+#try out index generation and fuse (note that --all == HEAD in this case)
+#probably should make a test for that too...
+test_expect_success 'test (non-)fusion of one slice' '
+ git-rev-cache fuse >output.err &&
+ grep "nothing to fuse" output.err
+'
+test_expect_success 'make fresh slice' '
+ git-rev-cache add --all --fresh 2>output.err &&
+ grep "final return value: 0" output.err
+'
+
+test_expect_success 'check dual slices' '
+ git-rev-list --topo-order HEAD~2 HEAD >list &&
+ test_cmp list proper_commit_list
+'
+
+test_expect_success 'regenerate index' '
+ rm .git/rev-cache/index &&
+ git-rev-cache index 2>output.err &&
+ grep "final return value: 0" output.err
+'
+
+test_expect_success 'fuse slices' '
+ test -e .git/rev-cache/.old &&
+ git-rev-cache fuse 2>output.err &&
+ grep "final return value: 0" output.err &&
+ test_cmp .git/rev-cache/$cache_sha1 .git/rev-cache/.old
+'
+
+#make sure we can smoothly handle corrupted caches
+test_expect_success 'corrupt slice' '
+ echo bla >.git/rev-cache/$cache_sha1
+'
+
+test_expect_success 'test rev-list traversal (limited) (corrupt slice)' '
+ git-rev-list --topo-order HEAD --not HEAD~3 >list &&
+ test_cmp list proper_commit_list_limited
+'
+
+test_expect_success 'test rev-list traversal (unlimited) (corrupt slice)' '
+ git-rev-list HEAD >list &&
+ test_cmp_sorted list proper_commit_list
+'
+
+test_expect_success 'corrupt index' '
+ echo blu >.git/rev-cache/index
+'
+
+test_expect_success 'test rev-list traversal (limited) (corrupt index)' '
+ git-rev-list --topo-order HEAD --not HEAD~3 >list &&
+ test_cmp list proper_commit_list_limited
+'
+
+test_expect_success 'test rev-list traversal (unlimited) (corrupt index)' '
+ git-rev-list HEAD >list &&
+ test_cmp_sorted list proper_commit_list
+'
+
+#test --ignore-size in fuse
+rm .git/rev-cache/*
+cache_sha1=`git-rev-cache add HEAD~2 2>output.err`
+
+test_expect_success 'make fragmented slices' '
+ git-rev-cache add HEAD~1 --not HEAD~2 2>>output.err &&
+ git-rev-cache add HEAD --fresh 2>>output.err &&
+ test `grep "final return value: 0" output.err | wc -l` -eq 3
+'
+
+cache_size=`wc -c .git/rev-cache/$cache_sha1 | grep -o "[0-9]*"`
+test_expect_success 'test --ignore-size function in fuse' '
+ git-rev-cache fuse --ignore-size=$cache_size 2>output.err &&
+ grep "final return value: 0" output.err &&
+ test -e .git/rev-cache/$cache_sha1
+'
+
+test_done
--
tg: (04607f1..) t/revcache/integration (depends on: t/revcache/misc)
^ permalink raw reply related
* Re: [PATCH 6/6 (v2)] support for path name caching of blobs/trees in rev-cache
From: Nick Edelen @ 2009-08-16 22:42 UTC (permalink / raw)
To: Nick Edelen, Junio C Hamano, Nicolas Pitre, Johannes Schindelin,
Sam Vilain
In-Reply-To: <op.uyb1vs0htdk399@sirnot.private>
An update to caching mechanism, allowing path names to be cached for blob and
tree objects. A list of names appearing in each cache slice is appended to the
end of the slice, which is referenced by variable-sized indexes per entry.
This allows pack-objects to more intelligently schedule unpacked/poorly packed
object, and enables proper duplication of rev-list's behaivor.
Signed-off-by: Nick Edelen <sirnot@gmail.com>
---
builtin-rev-cache.c | 3 +-
rev-cache.c | 314 +++++++++++++++++++++++++++++++++++++--------
rev-cache.h | 16 ++-
revision.h | 6 +-
t/t6015-rev-cache-list.sh | 4 +-
5 files changed, 282 insertions(+), 61 deletions(-)
diff --git a/builtin-rev-cache.c b/builtin-rev-cache.c
index a3489ce..5ea5c6b 100644
--- a/builtin-rev-cache.c
+++ b/builtin-rev-cache.c
@@ -177,13 +177,14 @@ static int handle_walk(int argc, const char *argv[])
fprintf(stderr, "pending:\n");
for (i = 0; i < revs.pending.nr; i++) {
struct object *obj = revs.pending.objects[i].item;
+ const char *name = revs.pending.objects[i].name;
/* unfortunately, despite our careful generation, object duplication *is* a possibility...
* (eg. same object introduced into two different branches) */
if (obj->flags & SEEN)
continue;
- printf("%s\n", sha1_to_hex(revs.pending.objects[i].item->sha1));
+ printf("%s %s\n", sha1_to_hex(revs.pending.objects[i].item->sha1), name);
obj->flags |= SEEN;
}
diff --git a/rev-cache.c b/rev-cache.c
index 7eefd3c..1db5578 100644
--- a/rev-cache.c
+++ b/rev-cache.c
@@ -17,6 +17,14 @@ struct bad_slice {
struct bad_slice *next;
};
+struct name_list {
+ unsigned char sha1[20];
+ unsigned int len;
+ struct name_list *next;
+
+ char buf[FLEX_ARRAY];
+};
+
struct cache_slice_pointer {
char signature[8]; /* REVCOPTR */
char version;
@@ -29,10 +37,13 @@ static uint32_t fanout[0xff + 2];
static unsigned char *idx_map;
static int idx_size;
static struct rc_index_header idx_head;
-static char no_idx, add_to_pending;
-static struct bad_slice *bad_slices;
+static char no_idx, add_to_pending, add_names;
static unsigned char *idx_caches;
+static struct bad_slice *bad_slices;
+static struct name_list *name_lists, *cur_name_list;
+
+static struct strbuf *acc_name_buffer;
static struct strbuf *acc_buffer;
#define SLOP 5
@@ -78,7 +89,7 @@ struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondis
if (!dst)
dst = &entry[cur++ & 0x3];
- dst->type = src->flags >> 5;
+ dst->type = src->flags >> 5 & 0x03;
dst->is_end = !!(src->flags & 0x10);
dst->is_start = !!(src->flags & 0x08);
dst->uninteresting = !!(src->flags & 0x04);
@@ -89,8 +100,9 @@ struct rc_object_entry *from_disked_rc_object_entry(struct rc_object_entry_ondis
dst->merge_nr = src->merge_nr;
dst->split_nr = src->split_nr;
- dst->size_size = src->sizes >> 5;
- dst->padding = src->sizes & 0x1f;
+ dst->size_size = src->sizes >> 5 & 0x03;
+ dst->name_size = src->sizes >> 2 & 0x03;
+ dst->padding = src->sizes & 0x02;
dst->date = ntohl(src->date);
dst->path = ntohs(src->path);
@@ -118,6 +130,7 @@ struct rc_object_entry_ondisk *to_disked_rc_object_entry(struct rc_object_entry
dst->split_nr = src->split_nr;
dst->sizes = (unsigned char)src->size_size << 5;
+ dst->sizes |= (unsigned char)src->name_size << 2;
dst->sizes |= (unsigned char)src->padding;
dst->date = htonl(src->date);
@@ -190,6 +203,12 @@ static void cleanup_cache_slices(void)
idx_map = 0;
}
+ while (name_lists) {
+ struct name_list *nl = name_lists->next;
+ free(name_lists);
+ name_lists = nl;
+ }
+
}
static int init_index(void)
@@ -322,7 +341,7 @@ static void handle_noncommit(struct rev_info *revs, struct commit *commit, unsig
struct blob *blob;
struct tree *tree;
struct object *obj;
- unsigned long size;
+ unsigned long size, name_index;
size = decode_size(ptr + RC_ENTRY_SIZE_OFFSET(entry), entry->size_size);
switch (entry->type) {
@@ -355,9 +374,22 @@ static void handle_noncommit(struct rev_info *revs, struct commit *commit, unsig
return;
}
+ if (add_names && cur_name_list) {
+ name_index = decode_size(ptr + RC_ENTRY_NAME_OFFSET(entry), entry->name_size);
+
+ if (name_index >= cur_name_list->len)
+ name_index = 0;
+ } else name_index = 0;
+
obj->flags |= FACE_VALUE;
- if (add_to_pending)
- add_pending_object(revs, obj, "");
+ if (add_to_pending) {
+ char *name = "";
+
+ if (name_index)
+ name = cur_name_list->buf + name_index;
+
+ add_pending_object(revs, obj, name);
+ }
}
static int setup_traversal(struct rc_slice_header *head, unsigned char *map, struct commit *commit, struct commit_list **work,
@@ -712,15 +744,44 @@ end:
return retval;
}
-static int get_cache_slice_header(unsigned char *cache_sha1, unsigned char *map, int len, struct rc_slice_header *head)
+static struct name_list *get_cache_slice_name_list(struct rc_slice_header *head, int fd)
+{
+ struct name_list *nl = name_lists;
+
+ while (nl) {
+ if (!hashcmp(nl->sha1, head->sha1))
+ break;
+ nl = nl->next;
+ }
+
+ if (nl)
+ return nl;
+
+ nl = xcalloc(1, sizeof(struct name_list) + head->name_size);
+ nl->len = head->name_size;
+ hashcpy(nl->sha1, head->sha1);
+
+ lseek(fd, head->size, SEEK_SET);
+ read_in_full(fd, nl->buf, head->name_size);
+
+ nl->next = name_lists;
+ name_lists = nl;
+
+ return nl;
+}
+
+static int get_cache_slice_header(int fd, unsigned char *cache_sha1, int len, struct rc_slice_header *head)
{
int t;
- memcpy(head, map, sizeof(struct rc_slice_header));
+ if (xread(fd, head, sizeof(struct rc_slice_header)) != sizeof(struct rc_slice_header))
+ return -1;
+
head->ofs_objects = ntohl(head->ofs_objects);
head->object_nr = ntohl(head->object_nr);
head->size = ntohl(head->size);
head->path_nr = ntohs(head->path_nr);
+ head->name_size = ntohl(head->name_size);
if (memcmp(head->signature, "REVCACHE", 8))
return -1;
@@ -729,10 +790,10 @@ static int get_cache_slice_header(unsigned char *cache_sha1, unsigned char *map,
if (hashcmp(head->sha1, cache_sha1))
return -3;
t = sizeof(struct rc_slice_header);
- if (t != head->ofs_objects || t >= len)
+ if (t != head->ofs_objects)
return -4;
-
- head->size = len;
+ if (head->size + head->name_size != len)
+ return -5;
return 0;
}
@@ -784,7 +845,7 @@ int traverse_cache_slice(struct rev_info *revs,
unsigned long *date_so_far, int *slop_so_far,
struct commit_list ***queue, struct commit_list **work)
{
- int fd = -1, retval = -3;
+ int fd = -1, t, retval;
struct stat fi;
struct rc_slice_header head;
struct rev_cache_info *rci;
@@ -800,26 +861,31 @@ int traverse_cache_slice(struct rev_info *revs,
/* load options */
rci = &revs->rev_cache_info;
add_to_pending = rci->add_to_pending;
+ add_names = rci->add_names;
memset(&head, 0, sizeof(struct rc_slice_header));
+# define ERROR(x) do { retval = (x); goto end; } while (0);
fd = open_cache_slice(cache_sha1, O_RDONLY);
if (fd == -1)
- goto end;
+ ERROR(-1);
if (fstat(fd, &fi) || fi.st_size < sizeof(struct rc_slice_header))
- goto end;
+ ERROR(-2);
+
+ if ((t = get_cache_slice_header(fd, cache_sha1, fi.st_size, &head)) < 0)
+ ERROR(-t);
+ if (add_names)
+ cur_name_list = get_cache_slice_name_list(&head, fd);
- map = xmmap(0, fi.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ map = xmmap(0, head.size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == MAP_FAILED)
- goto end;
- if (get_cache_slice_header(cache_sha1, map, fi.st_size, &head))
- goto end;
+ ERROR(-3);
retval = traverse_cache_slice_1(&head, map, revs, commit, date_so_far, slop_so_far, queue, work);
end:
if (map != MAP_FAILED)
- munmap(map, fi.st_size);
+ munmap(map, head.size);
if (fd != -1)
close(fd);
@@ -827,6 +893,7 @@ end:
if (retval)
mark_bad_slice(cache_sha1);
+# undef ERROR
return retval;
}
@@ -1111,23 +1178,110 @@ static unsigned long decode_size(unsigned char *str, int len)
return size;
}
+
+#define NL_HASH_TABLE_SIZE (0xffff + 1)
+#define NL_HASH_NUMBER (NL_HASH_TABLE_SIZE >> 3)
+
+struct name_list_hash {
+ int ind;
+ struct name_list_hash *next;
+};
+
+static struct name_list_hash **nl_hash_table;
+static unsigned char *nl_hashes;
+
+/* FNV-1a hash */
+static unsigned int hash_name(const char *name)
+{
+ unsigned int hash = 2166136261ul;
+ const char *p = name;
+
+ while (*p) {
+ hash ^= *p++;
+ hash *= 16777619ul;
+ }
+
+ return hash & 0xffff;
+}
+
+static int name_in_list(const char *name)
+{
+ unsigned int h = hash_name(name);
+ struct name_list_hash *entry = nl_hash_table[h];
+
+ while (entry && strcmp(acc_name_buffer->buf + entry->ind, name))
+ entry = entry->next;
+
+ if (entry)
+ return entry->ind;
+
+ /* add name to buffer and create hash reference */
+ entry = xcalloc(1, sizeof(struct name_list_hash));
+ entry->ind = acc_name_buffer->len;
+ strbuf_add(acc_name_buffer, name, strlen(name) + 1);
+
+ entry->next = nl_hash_table[h];
+ nl_hash_table[h] = entry;
+
+ nl_hashes[h / 8] |= h % 8;
+
+ return entry->ind;
+}
+
+static void init_name_list_hash(void)
+{
+ nl_hash_table = xcalloc(NL_HASH_TABLE_SIZE, sizeof(struct name_list_hash));
+ nl_hashes = xcalloc(NL_HASH_NUMBER, 1);
+}
+
+static void cleanup_name_list_hash(void)
+{
+ int i;
+
+ for (i = 0; i < NL_HASH_NUMBER; i++) {
+ int j, ind = nl_hashes[i];
+
+ if (!ind)
+ continue;
+
+ for (j = 0; j < 8; j++) {
+ struct name_list_hash **entryp;
+
+ if (!(ind & 1 << j))
+ continue;
+
+ entryp = &nl_hash_table[i * 8 + j];
+ while (*entryp) {
+ struct name_list_hash *t = (*entryp)->next;
+
+ free(*entryp);
+ *entryp = t;
+ }
+ }
+ } /* code overhang! */
+
+ free(nl_hashes);
+ free(nl_hash_table);
+}
+
static void add_object_entry(const unsigned char *sha1, struct rc_object_entry *entryp,
- struct strbuf *merge_str, struct strbuf *split_str)
+ struct strbuf *merge_str, struct strbuf *split_str, char *name, unsigned long size)
{
struct rc_object_entry entry;
- unsigned char size_str[7];
- unsigned long size;
+ unsigned char size_str[7], name_str[7];
enum object_type type;
void *data;
if (entryp)
sha1 = entryp->sha1;
- /* retrieve size data */
- data = read_sha1_file(sha1, &type, &size);
+ if (!size) {
+ /* retrieve size data */
+ data = read_sha1_file(sha1, &type, &size);
- if (data)
- free(data);
+ if (data)
+ free(data);
+ }
/* initialize! */
if (!entryp) {
@@ -1145,6 +1299,9 @@ static void add_object_entry(const unsigned char *sha1, struct rc_object_entry *
entryp->size_size = encode_size(size, size_str);
+ if (name)
+ entryp->name_size = encode_size(name_in_list(name), name_str);
+
/* write the muvabitch */
strbuf_add(acc_buffer, to_disked_rc_object_entry(entryp, 0), sizeof(struct rc_object_entry_ondisk));
@@ -1154,6 +1311,9 @@ static void add_object_entry(const unsigned char *sha1, struct rc_object_entry *
strbuf_add(acc_buffer, split_str->buf, split_str->len);
strbuf_add(acc_buffer, size_str, entryp->size_size);
+
+ if (name)
+ strbuf_add(acc_buffer, name_str, entryp->name_size);
}
/* returns non-zero to continue parsing, 0 to skip */
@@ -1198,6 +1358,9 @@ continue_loop:
static int dump_tree_callback(const unsigned char *sha1, const char *path, unsigned int mode)
{
strbuf_add(acc_buffer, sha1, 20);
+ strbuf_add(acc_buffer, (char *)&acc_name_buffer->len, sizeof(size_t));
+
+ strbuf_add(acc_name_buffer, path, strlen(path) + 1);
return 1;
}
@@ -1211,6 +1374,9 @@ static void tree_addremove(struct diff_options *options,
return;
strbuf_add(acc_buffer, sha1, 20);
+ strbuf_add(acc_buffer, (char *)&acc_name_buffer->len, sizeof(size_t));
+
+ strbuf_add(acc_name_buffer, concatpath, strlen(concatpath) + 1);
}
static void tree_change(struct diff_options *options,
@@ -1223,12 +1389,15 @@ static void tree_change(struct diff_options *options,
return;
strbuf_add(acc_buffer, new_sha1, 20);
+ strbuf_add(acc_buffer, (char *)&acc_name_buffer->len, sizeof(size_t));
+
+ strbuf_add(acc_name_buffer, concatpath, strlen(concatpath) + 1);
}
static int add_unique_objects(struct commit *commit)
{
struct commit_list *list;
- struct strbuf os, ost, *orig_buf;
+ struct strbuf os, ost, names, *orig_name_buf, *orig_buf;
struct diff_options opts;
int i, j, next;
char is_first = 1;
@@ -1236,13 +1405,17 @@ static int add_unique_objects(struct commit *commit)
/* ...no, calculate unique objects */
strbuf_init(&os, 0);
strbuf_init(&ost, 0);
+ strbuf_init(&names, 0);
orig_buf = acc_buffer;
+ orig_name_buf = acc_name_buffer;
+ acc_name_buffer = &names;
diff_setup(&opts);
DIFF_OPT_SET(&opts, RECURSIVE);
DIFF_OPT_SET(&opts, TREE_IN_RECURSIVE);
opts.change = tree_change;
opts.add_remove = tree_addremove;
+# define ENTRY_SIZE (20 + sizeof(size_t))
/* this is only called for non-ends (ie. all parents interesting) */
for (list = commit->parents; list; list = list->next) {
@@ -1253,20 +1426,20 @@ static int add_unique_objects(struct commit *commit)
strbuf_setlen(acc_buffer, 0);
diff_tree_sha1(list->item->tree->object.sha1, commit->tree->object.sha1, "", &opts);
- qsort(acc_buffer->buf, acc_buffer->len / 20, 20, (int (*)(const void *, const void *))hashcmp);
+ qsort(acc_buffer->buf, acc_buffer->len / ENTRY_SIZE, ENTRY_SIZE, (int (*)(const void *, const void *))hashcmp);
/* take intersection */
if (!is_first) {
- for (next = i = j = 0; i < os.len; i += 20) {
+ for (next = i = j = 0; i < os.len; i += ENTRY_SIZE) {
while (j < ost.len && hashcmp((unsigned char *)(ost.buf + j), (unsigned char *)(os.buf + i)) < 0)
- j += 20;
+ j += ENTRY_SIZE;
if (j >= ost.len || hashcmp((unsigned char *)(ost.buf + j), (unsigned char *)(os.buf + i)))
continue;
if (next != i)
- memcpy(os.buf + next, os.buf + i, 20);
- next += 20;
+ memcpy(os.buf + next, os.buf + i, ENTRY_SIZE);
+ next += ENTRY_SIZE;
}
if (next != i)
@@ -1283,26 +1456,34 @@ static int add_unique_objects(struct commit *commit)
/* the ordering of non-commit objects dosn't really matter, so we're not gonna bother */
acc_buffer = orig_buf;
- for (i = 0; i < os.len; i += 20)
- add_object_entry((unsigned char *)(os.buf + i), 0, 0, 0);
+ acc_name_buffer = orig_name_buf;
+ for (i = 0; i < os.len; i += ENTRY_SIZE)
+ add_object_entry((unsigned char *)(os.buf + i), 0, 0, 0, names.buf + *(size_t *)(os.buf + i + 20), 0);
/* last but not least, the main tree */
- add_object_entry(commit->tree->object.sha1, 0, 0, 0);
+ add_object_entry(commit->tree->object.sha1, 0, 0, 0, 0, 0);
+
+ strbuf_release(&ost);
+ strbuf_release(&os);
+ strbuf_release(&names);
- return i / 20 + 1;
+ return i / ENTRY_SIZE + 1;
+# undef ENTRY_SIZE
}
static int add_objects_verbatim_1(struct rev_cache_slice_map *mapping, int *index)
{
- unsigned char *map = mapping->map;
int i = *index, object_nr = 0;
+ unsigned char *map = mapping->map;
struct rc_object_entry *entry = RC_OBTAIN_OBJECT_ENTRY(map + i);
+ unsigned long size;
i += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);
while (i < mapping->size) {
- int pos = i;
+ char *name;
+ int name_index, pos = i;
- entry = RC_OBTAIN_OBJECT_ENTRY(map + i;
+ entry = RC_OBTAIN_OBJECT_ENTRY(map + i);
i += RC_ACTUAL_OBJECT_ENTRY_SIZE(entry);
if (entry->type == OBJ_COMMIT) {
@@ -1310,7 +1491,15 @@ static int add_objects_verbatim_1(struct rev_cache_slice_map *mapping, int *inde
return object_nr;
}
- strbuf_add(acc_buffer, map + pos, i - pos);
+ name_index = decode_size(map + pos + RC_ENTRY_NAME_OFFSET(entry), entry->name_size);
+ if (name_index && name_index < mapping->name_size)
+ name = mapping->names + name_index;
+ else
+ name = 0;
+
+ size = decode_size(map + pos + RC_ENTRY_SIZE_OFFSET(entry), entry->size_size);
+
+ add_object_entry(0, entry, 0, 0, name, size);
object_nr++;
}
@@ -1395,6 +1584,7 @@ void init_rev_cache_info(struct rev_cache_info *rci)
rci->overwrite_all = 0;
rci->add_to_pending = 1;
+ rci->add_names = 1;
rci->ignore_size = 0;
}
@@ -1419,9 +1609,9 @@ int make_cache_slice(struct rev_cache_info *rci,
struct rc_slice_header head;
struct commit *commit;
unsigned char sha1[20];
- struct strbuf merge_paths, split_paths;
+ struct strbuf merge_paths, split_paths, namelist;
int object_nr, total_sz, fd;
- char file[PATH_MAX], *newfile;
+ char file[PATH_MAX], null, *newfile;
struct rev_cache_info *trci;
git_SHA_CTX ctx;
@@ -1436,7 +1626,13 @@ int make_cache_slice(struct rev_cache_info *rci,
strbuf_init(&endlist, 0);
strbuf_init(&merge_paths, 0);
strbuf_init(&split_paths, 0);
+ strbuf_init(&namelist, 0);
acc_buffer = &buffer;
+ acc_name_buffer = &namelist;
+
+ null = 0;
+ strbuf_add(&namelist, &null, 1);
+ init_name_list_hash();
if (!revs) {
revs = &therevs;
@@ -1467,6 +1663,7 @@ int make_cache_slice(struct rev_cache_info *rci,
trci = &revs->rev_cache_info;
init_rev_cache_info(trci);
trci->add_to_pending = 0;
+ trci->add_names = 0;
setup_revisions(0, 0, revs, 0);
if (prepare_revision_walk(revs))
@@ -1504,7 +1701,7 @@ int make_cache_slice(struct rev_cache_info *rci,
commit->indegree = 0;
- add_object_entry(0, &object, &merge_paths, &split_paths);
+ add_object_entry(0, &object, &merge_paths, &split_paths, 0, 0);
object_nr++;
if (rci->objects && !object.is_end) {
@@ -1530,10 +1727,16 @@ int make_cache_slice(struct rev_cache_info *rci,
total_sz += buffer.len;
}
+ /* write path name lookup list */
+ head.name_size = htonl(namelist.len);
+ write_in_full(fd, namelist.buf, namelist.len);
+
/* go ahead a free some stuff... */
strbuf_release(&buffer);
strbuf_release(&merge_paths);
strbuf_release(&split_paths);
+ strbuf_release(&namelist);
+ cleanup_name_list_hash();
if (path_sz)
free(paths);
while (path_track_alloc)
@@ -1991,6 +2194,7 @@ int fuse_cache_slices(struct rev_cache_info *rci, struct rev_info *revs)
for (i = idx_head.cache_nr - 1; i >= 0; i--) {
struct rev_cache_slice_map *map = rci->maps + i;
struct stat fi;
+ struct rc_slice_header head;
int fd;
if (!map->size)
@@ -2003,13 +2207,20 @@ int fuse_cache_slices(struct rev_cache_info *rci, struct rev_info *revs)
continue;
if (fi.st_size < sizeof(struct rc_slice_header))
continue;
+ if (get_cache_slice_header(fd, idx_caches + i * 20, fi.st_size, &head))
+ continue;
- map->map = xmmap(0, fi.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ map->map = xmmap(0, head.size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map->map == MAP_FAILED)
continue;
+ lseek(fd, head.size, SEEK_SET);
+ map->names = xcalloc(head.name_size, 1);
+ read_in_full(fd, map->names, head.name_size);
+
close(fd);
- map->size = fi.st_size;
+ map->size = head.size;
+ map->name_size = head.name_size;
}
rci->make_index = 0;
@@ -2026,6 +2237,7 @@ int fuse_cache_slices(struct rev_cache_info *rci, struct rev_info *revs)
if (!map->size)
continue;
+ free(map->names);
munmap(map->map, map->size);
}
free(rci->maps);
@@ -2047,7 +2259,6 @@ static int verify_cache_slice(const char *slice_path, unsigned char *sha1)
{
struct rc_slice_header head;
int fd, len, retval = -1;
- unsigned char *map = MAP_FAILED;
struct stat fi;
len = strlen(slice_path);
@@ -2062,17 +2273,12 @@ static int verify_cache_slice(const char *slice_path, unsigned char *sha1)
if (fstat(fd, &fi) || fi.st_size < sizeof(head))
goto end;
- map = xmmap(0, sizeof(head), PROT_READ, MAP_PRIVATE, fd, 0);
- if (map == MAP_FAILED)
- goto end;
- if (get_cache_slice_header(sha1, map, fi.st_size, &head))
+ if (get_cache_slice_header(fd, sha1, fi.st_size, &head))
goto end;
retval = 0;
end:
- if (map != MAP_FAILED)
- munmap(map, sizeof(head));
if (fd > 0)
close(fd);
diff --git a/rev-cache.h b/rev-cache.h
index 14437d8..c88ceae 100644
--- a/rev-cache.h
+++ b/rev-cache.h
@@ -10,8 +10,14 @@
#define RC_OBTAIN_OBJECT_ENTRY(p) from_disked_rc_object_entry((struct rc_object_entry_ondisk *)(p), 0)
#define RC_OBTAIN_INDEX_ENTRY(p) from_disked_rc_index_entry((struct rc_index_entry_ondisk *)(p), 0)
-#define RC_ACTUAL_OBJECT_ENTRY_SIZE(e) (sizeof(struct rc_object_entry_ondisk) + RC_PATH_SIZE((e)->merge_nr + (e)->split_nr) + (e)->size_size)
-#define RC_ENTRY_SIZE_OFFSET(e) (RC_ACTUAL_OBJECT_ENTRY_SIZE(e) - (e)->size_size)
+#define RC_ACTUAL_OBJECT_ENTRY_SIZE(e) (\
+ sizeof(struct rc_object_entry_ondisk) + \
+ RC_PATH_SIZE((e)->merge_nr + (e)->split_nr) + \
+ (e)->size_size + \
+ (e)->name_size\
+)
+#define RC_ENTRY_SIZE_OFFSET(e) (RC_ACTUAL_OBJECT_ENTRY_SIZE(e) - (e)->name_size - (e)->size_size)
+#define RC_ENTRY_NAME_OFFSET(e) (RC_ACTUAL_OBJECT_ENTRY_SIZE(e) - (e)->name_size)
/* single index maps objects to cache files */
struct rc_index_header {
@@ -50,6 +56,8 @@ struct rc_slice_header {
uint32_t size;
unsigned char sha1[20];
+
+ uint32_t name_size;
};
struct rc_object_entry_ondisk {
@@ -76,7 +84,8 @@ struct rc_object_entry {
unsigned char merge_nr; /* : 7 */
unsigned char split_nr; /* : 7 */
unsigned size_size : 3;
- unsigned padding : 5;
+ unsigned name_size : 3;
+ unsigned padding : 2;
uint32_t date;
uint16_t path;
@@ -84,6 +93,7 @@ struct rc_object_entry {
/* merge paths */
/* split paths */
/* size */
+ /* name id */
};
struct rc_index_entry *from_disked_rc_index_entry(struct rc_index_entry_ondisk *src, struct rc_index_entry *dst);
diff --git a/revision.h b/revision.h
index c3ec1b3..b2d5834 100644
--- a/revision.h
+++ b/revision.h
@@ -23,6 +23,9 @@ struct rev_cache_slice_map {
unsigned char *map;
int size;
int last_index;
+
+ char *names;
+ int name_size;
};
struct rev_cache_info {
@@ -36,7 +39,8 @@ struct rev_cache_info {
unsigned overwrite_all : 1;
/* traversal flags */
- unsigned add_to_pending : 1;
+ unsigned add_to_pending : 1,
+ add_names : 1;
/* fuse options */
unsigned int ignore_size;
diff --git a/t/t6015-rev-cache-list.sh b/t/t6015-rev-cache-list.sh
index fa6df21..ff36881 100755
--- a/t/t6015-rev-cache-list.sh
+++ b/t/t6015-rev-cache-list.sh
@@ -4,8 +4,8 @@ test_description='git rev-cache tests'
. ./test-lib.sh
test_cmp_sorted() {
- grep -io "[a-f0-9]*" $1 | sort >.tmpfile1 &&
- grep -io "[a-f0-9]*" $2 | sort >.tmpfile2 &&
+ sort $1 >.tmpfile1 &&
+ sort $2 >.tmpfile2 &&
test_cmp .tmpfile1 .tmpfile2
}
--
tg: (e6091b4..) t/revcache/names (depends on: t/revcache/docs)
^ permalink raw reply related
* Re: [PATCH 0/6 (v2)] Suggested for PU: revision caching system to significantly speed up packing/walking
From: Nick Edelen @ 2009-08-16 22:47 UTC (permalink / raw)
To: Junio C Hamano, Nicolas Pitre, Johannes Schindelin, Sam Vilain,
Michael J Gruber
In-Reply-To: <op.uyb1uryftdk399@sirnot.private>
Well crap, in my infinite wisdom I replied with the final updated
patchset to the wrong version :-/ I don't want to clog up the list
with my stupid mistakes, but if you need me to I can resend them to
v3.
But yeah, I've uploaded an updated version. This patchset:
- fixes whitespace errors
- adds _ondisk versions of structures to ensure portability
- cleans up definitions/fix a typo
Hope it works out for you all. Sorry about the mismatched reply.
- Nick
^ permalink raw reply
* Re: Linus' sha1 is much faster!
From: Linus Torvalds @ 2009-08-16 22:47 UTC (permalink / raw)
To: Giuseppe Scrivano; +Cc: Bug-coreutils, Pádraig Brady, Git Mailing List
In-Reply-To: <87ab1ze76y.fsf@master.homenet>
On Mon, 17 Aug 2009, Giuseppe Scrivano wrote:
>
> Thanks for the hint. I tried gcc-4.4 and it produces slower code than
> 4.3 on the gnulib SHA1 implementation and my patch makes it even more!
Check out the asm, see if you can see why. One of the most common problems
with P4's is literally that you end up loading from the same stack slot
that you just stored to (gcc can do some really crazy spills), and that
causes a store buffer hazard replay.
My personal opinion is that Netburst is useless for trying to optimize C
code for. It's just too random.
> I noticed that on my machine your implementation is ~30-40% faster using
> SHA_ROT for rol/ror instructions than inline assembly, at least with the
> test-case Pádraig wrote. Am I the only one reporting it?
I bet it's the same thing. Small perturbations of the source causing small
changes to register allocation and thus spilling, and then Netburst goes
crazy one way or another. It's interestign trying to fix it, and very
frustrating.
My workstation is a Nehalem (but Core 2 will have pretty much the same
behavior), and it doesn't have the crazy netburst behavior. Shorter and
simpler code generally performs better (which is _not_ true on Netburst).
On my machine, for example, forcing gcc to do those rotates on registers
is the difference between ~381MB/s and 415MB/s. And that's mainly because
it makes gcc keep A-E in registers, rather than trying to cache the
array[] references.
Linus
^ permalink raw reply
* quick question about __stdcall at run-command.c mingw.c
From: Frank Li @ 2009-08-16 23:19 UTC (permalink / raw)
To: git, msysGit
I am tring to clear VC build patch.
I found __stdcall position break MSVC build.
static __stdcall unsigned run_thread(void *data)
MSVC require __stdcall should be between return type and function name.
like
static unsigned __stdcall run_thread(void *data)
I think msys gcc should support MSVC format.
Should I directly change to MSVC format or add _MSC_VER marcro like
#if defined(__MINGW32__)
static __stdcall unsigned run_thread(void *data)
#elif defined(_MSC_VER) /*MSVC must put __stdcall between return value
and function*/
static unsigned __stdcall run_thread(void *data)
#endif
^ permalink raw reply
* Re: [PATCH 0/6 (v2)] Suggested for PU: revision caching system to significantly speed up packing/walking
From: Sam Vilain @ 2009-08-16 23:19 UTC (permalink / raw)
To: Nick Edelen
Cc: Junio C Hamano, Nicolas Pitre, Johannes Schindelin,
Michael J Gruber, Jeff King, Shawn O. Pearce, Andreas Ericsson,
Christian Couder, git@vger.kernel.org
In-Reply-To: <c77435a80908161547x4d2a9087qf439d1c30cc4fa99@mail.gmail.com>
Nick Edelen wrote:
> Well crap, in my infinite wisdom I replied with the final updated
> patchset to the wrong version :-/ I don't want to clog up the list
> with my stupid mistakes, but if you need me to I can resend them to
> v3.
>
Heh, that sure reads like a sentence written after midnight :-)
These all say v2, and the last lot was labeled as v3. And I see an
older submission labeled as v2 (8th August) and got duplicates of those
labeled as v3.
Are the ones attached to this the ones that are the best?
Sam
> But yeah, I've uploaded an updated version. This patchset:
> - fixes whitespace errors
> - adds _ondisk versions of structures to ensure portability
> - cleans up definitions/fix a typo
>
> Hope it works out for you all. Sorry about the mismatched reply.
>
> - Nick
>
^ permalink raw reply
* [PATCHv3 0/3] git-gui: more robust handling of fancy repos
From: Giuseppe Bilotta @ 2009-08-16 23:58 UTC (permalink / raw)
To: git; +Cc: Markus Heidelberg, Shawn O. Pearce, Giuseppe Bilotta
As promised a long time ago (March 30), version 3 of the small patchset
to improve handling of repositories in git gui. The most significant
change is the addition of the third patch.
The first patch allows git gui to work with respotiories for which
the worktree is not the parent of the gitdir.
The second patch refactors bare repository detection, improves the error
message if the bare support feature is disabled, and disabled
inapplicable menu entries.
The third patch allows git-gui to work properly when launched from the
.git directory itself, solving the issue Markus Heidelberg was having in
http://thread.gmane.org/gmane.comp.version-control.git/115044
Giuseppe Bilotta (3):
git-gui: handle non-standard worktree locations
git-gui: handle bare repos correctly
git-gui: work from the .git dir
git-gui/git-gui.sh | 88 +++++++++++++++++++++++++++++++++++++++++++---------
1 files changed, 73 insertions(+), 15 deletions(-)
^ permalink raw reply
* [PATCHv3 1/3] git-gui: handle non-standard worktree locations
From: Giuseppe Bilotta @ 2009-08-16 23:58 UTC (permalink / raw)
To: git; +Cc: Markus Heidelberg, Shawn O. Pearce, Giuseppe Bilotta
In-Reply-To: <1250467128-29839-1-git-send-email-giuseppe.bilotta@gmail.com>
Don't rely on the git worktree being the updir of the gitdir, since it
might not be. Instead, define (and use) a new _gitworktree global
variable, setting it to $GIT_WORK_TREE if present, falling back to
core.worktree if defined, and finally to whatever we guess the correct
worktree is. Getting core.worktree requires the config from the alleged
git dir _gitdir to be loaded early.
Supporting non-standard worktree locations also breaks the git-gui
assumption (made when calling gitk) that the worktree was the dirname of
$_gitdir and that, by consequence, the git dir could be set to the tail
of $_gitdir once we changed to the worktree root directory. Therefore,
we need to export a GIT_DIR environment variable set to the full,
normalized path of $_gitdir instead. We also skip changing to the worktree
directory if it's empty (i.e. if we're working on a bare repository).
Signed-off-by: Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
---
git-gui/git-gui.sh | 37 ++++++++++++++++++++++++++++---------
1 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 14b92ba..229e94a 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -121,6 +121,7 @@ unset oguimsg
set _appname {Git Gui}
set _gitdir {}
+set _gitworktree {}
set _gitexec {}
set _githtmldir {}
set _reponame {}
@@ -1088,13 +1089,25 @@ if {![file isdirectory $_gitdir]} {
error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"]
exit 1
}
+# _gitdir exists, so try loading the config
+load_config 0
+apply_config
+# try to set work tree from environment, falling back to core.worktree
+if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} {
+ set _gitworktree [get_config core.worktree]
+}
if {$_prefix ne {}} {
- regsub -all {[^/]+/} $_prefix ../ cdup
+ if {$_gitworktree eq {}} {
+ regsub -all {[^/]+/} $_prefix ../ cdup
+ } else {
+ set cdup $_gitworktree
+ }
if {[catch {cd $cdup} err]} {
catch {wm withdraw .}
error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"]
exit 1
}
+ set _gitworktree [pwd]
unset cdup
} elseif {![is_enabled bare]} {
if {[lindex [file split $_gitdir] end] ne {.git}} {
@@ -1102,11 +1115,15 @@ if {$_prefix ne {}} {
error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
exit 1
}
- if {[catch {cd [file dirname $_gitdir]} err]} {
+ if {$_gitworktree eq {}} {
+ set _gitworktree [file dirname $_gitdir]
+ }
+ if {[catch {cd $_gitworktree} err]} {
catch {wm withdraw .}
- error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"]
+ error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"]
exit 1
}
+ set _gitworktree [pwd]
}
set _reponame [file split [file normalize $_gitdir]]
if {[lindex $_reponame end] eq {.git}} {
@@ -1905,6 +1922,7 @@ proc incr_font_size {font {amt 1}} {
set starting_gitk_msg [mc "Starting gitk... please wait..."]
proc do_gitk {revs} {
+ global _gitworktree
# -- Always start gitk through whatever we were loaded with. This
# lets us bypass using shell process on Windows systems.
#
@@ -1922,8 +1940,10 @@ proc do_gitk {revs} {
}
set pwd [pwd]
- cd [file dirname [gitdir]]
- set env(GIT_DIR) [file tail [gitdir]]
+ if { $_gitworktree ne {} } {
+ cd $_gitworktree
+ }
+ set env(GIT_DIR) [file normalize [gitdir]]
eval exec $cmd $revs &
@@ -1942,6 +1962,7 @@ proc do_gitk {revs} {
}
proc do_explore {} {
+ global _gitworktree
set explorer {}
if {[is_Cygwin] || [is_Windows]} {
set explorer "explorer.exe"
@@ -1951,7 +1972,7 @@ proc do_explore {} {
# freedesktop.org-conforming system is our best shot
set explorer "xdg-open"
}
- eval exec $explorer [list [file nativename [file dirname [gitdir]]]] &
+ eval exec $explorer $_gitworktree &
}
set is_quitting 0
@@ -2297,8 +2318,6 @@ proc show_less_context {} {
##
## ui construction
-load_config 0
-apply_config
set ui_comm {}
# -- Menu Bar
@@ -3328,7 +3347,7 @@ unset i
set file_lists($ui_index) [list]
set file_lists($ui_workdir) [list]
-wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]"
+wm title . "[appname] ([reponame]) [file normalize $_gitworktree]"
focus -force $ui_comm
# -- Warn the user about environmental problems. Cygwin's Tcl
--
1.6.4.177.g695987
^ permalink raw reply related
* [PATCHv3 3/3] git-gui: work from the .git dir
From: Giuseppe Bilotta @ 2009-08-16 23:58 UTC (permalink / raw)
To: git; +Cc: Markus Heidelberg, Shawn O. Pearce, Giuseppe Bilotta
In-Reply-To: <1250467128-29839-1-git-send-email-giuseppe.bilotta@gmail.com>
When git-gui is run from a .git dir, _gitdir would be set to "." by
rev-parse, something that confuses the worktree detection.
Fix by expanding the value of _gitdir to pwd in this special case.
Signed-off-by: Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
---
git-gui/git-gui.sh | 10 ++++++++++
1 files changed, 10 insertions(+), 0 deletions(-)
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 38b11c1..fbad6fd 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -1100,6 +1100,8 @@ if {[catch {
set _prefix {}
}]
&& [catch {
+ # beware that from the .git dir this sets _gitdir to .
+ # and _prefix to the empty string
set _gitdir [git rev-parse --git-dir]
set _prefix [git rev-parse --show-prefix]
} err]} {
@@ -1108,6 +1110,14 @@ if {[catch {
choose_repository::pick
set picked 1
}
+
+# we expand the _gitdir when it's just a single dot (i.e. when we're being
+# run from the .git dir itself) lest the routines to find the worktree
+# get confused
+if {$_gitdir eq "."} {
+ set _gitdir [pwd]
+}
+
if {![file isdirectory $_gitdir] && [is_Cygwin]} {
catch {set _gitdir [exec cygpath --windows $_gitdir]}
}
--
1.6.4.177.g695987
^ permalink raw reply related
* [PATCHv3 2/3] git-gui: handle bare repos correctly
From: Giuseppe Bilotta @ 2009-08-16 23:58 UTC (permalink / raw)
To: git; +Cc: Markus Heidelberg, Shawn O. Pearce, Giuseppe Bilotta
In-Reply-To: <1250467128-29839-1-git-send-email-giuseppe.bilotta@gmail.com>
Refactor checking for a bare repository into its own proc, that relies
on git rev-parse --is-bare-repository if possible. For older versions of
git we fall back to a logic such that the repository is considered bare
if:
* either the core.bare setting is true
* or the worktree is not set and the directory name ends with .git
The error message for the case of an unhandled bare repository is also
updated to reflect the fact that the problem is not the funny name but
the bareness.
The new refactored proc is also used to disable the menu entry to
explore the working copy, and to skip changing to the worktree before
the gitk invocation.
Signed-off-by: Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
---
git-gui/git-gui.sh | 43 ++++++++++++++++++++++++++++++++++++-------
1 files changed, 36 insertions(+), 7 deletions(-)
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 229e94a..38b11c1 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -122,6 +122,7 @@ unset oguimsg
set _appname {Git Gui}
set _gitdir {}
set _gitworktree {}
+set _isbare {}
set _gitexec {}
set _githtmldir {}
set _reponame {}
@@ -277,6 +278,32 @@ proc get_config {name} {
}
}
+proc is_bare {} {
+ global _isbare
+ global _gitdir
+ global _gitworktree
+
+ if {$_isbare eq {}} {
+ if {[catch {
+ set _bare [git rev-parse --is-bare-repository]
+ switch -- $_bare {
+ true { set _isbare 1 }
+ false { set _isbare 0}
+ default { throw }
+ }
+ }]} {
+ if {[is_config_true core.bare]
+ || ($_gitworktree eq {}
+ && [lindex [file split $_gitdir] end] ne {.git})} {
+ set _isbare 1
+ } else {
+ set _isbare 0
+ }
+ }
+ }
+ return $_isbare
+}
+
######################################################################
##
## handy utils
@@ -1110,9 +1137,9 @@ if {$_prefix ne {}} {
set _gitworktree [pwd]
unset cdup
} elseif {![is_enabled bare]} {
- if {[lindex [file split $_gitdir] end] ne {.git}} {
+ if {[is_bare]} {
catch {wm withdraw .}
- error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"]
+ error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"]
exit 1
}
if {$_gitworktree eq {}} {
@@ -1940,7 +1967,7 @@ proc do_gitk {revs} {
}
set pwd [pwd]
- if { $_gitworktree ne {} } {
+ if { ![is_bare] } {
cd $_gitworktree
}
set env(GIT_DIR) [file normalize [gitdir]]
@@ -2349,10 +2376,12 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
#
menu .mbar.repository
-.mbar.repository add command \
- -label [mc "Explore Working Copy"] \
- -command {do_explore}
-.mbar.repository add separator
+if {![is_bare]} {
+ .mbar.repository add command \
+ -label [mc "Explore Working Copy"] \
+ -command {do_explore}
+ .mbar.repository add separator
+}
.mbar.repository add command \
-label [mc "Browse Current Branch's Files"] \
--
1.6.4.177.g695987
^ permalink raw reply related
* Re: [msysGit] quick question about __stdcall at run-command.c mingw.c
From: Pat Thoyts @ 2009-08-17 0:03 UTC (permalink / raw)
To: Frank Li; +Cc: git, msysGit
In-Reply-To: <1976ea660908161619x5182762sade051f24fef13db@mail.gmail.com>
2009/8/17 Frank Li <lznuaa@gmail.com>:
>
> I am tring to clear VC build patch.
>
> I found __stdcall position break MSVC build.
>
> static __stdcall unsigned run_thread(void *data)
>
> MSVC require __stdcall should be between return type and function name.
> like
> static unsigned __stdcall run_thread(void *data)
>
> I think msys gcc should support MSVC format.
>
> Should I directly change to MSVC format or add _MSC_VER marcro like
>
> #if defined(__MINGW32__)
> static __stdcall unsigned run_thread(void *data)
> #elif defined(_MSC_VER) /*MSVC must put __stdcall between return value
> and function*/
> static unsigned __stdcall run_thread(void *data)
> #endif
The win32 api prototype used for thread entry functions is declared as
a DWORD (WINAPI *LPTHREAD_START_ROUTINE)(LPVOID) type in the mingw
headers and WINAPI as #define WINAPI __stdcall. This is true for the
MSVC headers as well. So gcc and msvc are happy using the same
definition for such a function and just "static unsigned long WINAPI
run_thread(void *)" might well be sensible.
^ permalink raw reply
* Re: [PATCH 0/6 (v2)] Suggested for PU: revision caching system to significantly speed up packing/walking
From: Nick Edelen @ 2009-08-17 0:56 UTC (permalink / raw)
To: Sam Vilain
Cc: Junio C Hamano, Nicolas Pitre, Johannes Schindelin,
Michael J Gruber, Jeff King, Shawn O. Pearce, Andreas Ericsson,
Christian Couder, git@vger.kernel.org
In-Reply-To: <4A889412.9090209@vilain.net>
> Heh, that sure reads like a sentence written after midnight :-)
>
> These all say v2, and the last lot was labeled as v3. And I see an
> older submission labeled as v2 (8th August) and got duplicates of those
> labeled as v3.
>
> Are the ones attached to this the ones that are the best?
Yeah, the latest ones Re: v2, uploaded a few hours ago, are the best.
They should be Re: v3 but they're not... If I'm being confusing I can
just upload them to v3, but I didn't want to bombard everyone with
duplicates.
^ permalink raw reply
* "make quick-install-man" broke recently
From: Randal L. Schwartz @ 2009-08-17 1:16 UTC (permalink / raw)
To: git@vger.kernel.org
Something broke recently in "make quick-install-man". It works
fine if my mandir is empty, but not if a previous installation
is there.
Perhaps missing something that deletes the previous values?
Or maybe a change in the way "git checkout-index" works?
--
Randal L. Schwartz - Stonehenge Consulting Services, Inc. - +1 503 777 0095
<merlyn@stonehenge.com> <URL:http://www.stonehenge.com/merlyn/>
Smalltalk/Perl/Unix consulting, Technical writing, Comedy, etc. etc.
See http://methodsandmessages.vox.com/ for Smalltalk and Seaside discussion
^ permalink raw reply
* Re: "make quick-install-man" broke recently
From: Junio C Hamano @ 2009-08-17 1:29 UTC (permalink / raw)
To: Randal L. Schwartz; +Cc: git@vger.kernel.org
In-Reply-To: <86my5z8cjd.fsf@blue.stonehenge.com>
merlyn@stonehenge.com (Randal L. Schwartz) writes:
> Something broke recently in "make quick-install-man". It works
> fine if my mandir is empty, but not if a previous installation
> is there.
Broken how?
Sorry, but I obviously do not use the target myself, because I am the one
who builds manpages for real and stuff them in the 'man' branch for you to
check out after all.
"Something broke" is bit too vague a problem description if you expect me
to look into it.
^ permalink raw reply
* Re: "make quick-install-man" broke recently
From: Randal L. Schwartz @ 2009-08-17 1:33 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git@vger.kernel.org
In-Reply-To: <7vy6pj449g.fsf@alter.siamese.dyndns.org>
>>>>> "Junio" == Junio C Hamano <gitster@pobox.com> writes:
Junio> "Something broke" is bit too vague a problem description if you expect
Junio> me to look into it.
Very sorry. Let me include some text.
% rm -rf /opt/git/share/man
% make prefix=/opt/git quick-install-man
make -C Documentation quick-install-man
SUBDIR ../
make[2]: `GIT-VERSION-FILE' is up to date.
'/bin/sh' ./install-doc-quick.sh origin/man /opt/git/share/man
% make prefix=/opt/git quick-install-man
make -C Documentation quick-install-man
SUBDIR ../
make[2]: `GIT-VERSION-FILE' is up to date.
'/bin/sh' ./install-doc-quick.sh origin/man /opt/git/share/man
error: git checkout-index: unable to create file /opt/git/share/man/man1/git-add.1 (File exists)
error: git checkout-index: unable to create file /opt/git/share/man/man1/git-am.1 (File exists)
error: git checkout-index: unable to create file /opt/git/share/man/man1/git-annotate.1 (File exists)
error: git checkout-index: unable to create file /opt/git/share/man/man1/git-apply.1 (File exists)
[...]
So it fails the second time. This is new behavior.
--
Randal L. Schwartz - Stonehenge Consulting Services, Inc. - +1 503 777 0095
<merlyn@stonehenge.com> <URL:http://www.stonehenge.com/merlyn/>
Smalltalk/Perl/Unix consulting, Technical writing, Comedy, etc. etc.
See http://methodsandmessages.vox.com/ for Smalltalk and Seaside discussion
^ permalink raw reply
* Re: Linus' sha1 is much faster!
From: Nicolas Pitre @ 2009-08-17 1:55 UTC (permalink / raw)
To: Linus Torvalds
Cc: John Tapsell, Bryan Donlan, Pádraig Brady, Bug-coreutils,
Git Mailing List, Brandon Casey, Junio C Hamano, Paul Kocher
In-Reply-To: <alpine.LFD.2.01.0908151336530.3162@localhost.localdomain>
On Sat, 15 Aug 2009, Linus Torvalds wrote:
> (Heh. Looking at that, I probably should move the 'size' field first,
> since that would have different alignment rules, and the struct would be
> more tightly packed that way, and initialize better).
I was about to suggest (i.e. post) a patch for that. This is indeed a
good idea.
> Afaik, none of the actual code remains (the mozilla SHA1 thing did the
> wrong thing for performance even for just the final bytes, and did those a
> byte at a time etc, so I rewrote even the trivial SHA1_Final parts).
Maybe a patch adding a proper header with the actual license would be a
good idea too.
Nicolas
^ permalink raw reply
* Re: Linus' sha1 is much faster!
From: Pádraig Brady @ 2009-08-17 1:53 UTC (permalink / raw)
To: Giuseppe Scrivano; +Cc: Bug-coreutils, Linus Torvalds, Git Mailing List
In-Reply-To: <87eirbef3c.fsf@master.homenet>
Giuseppe Scrivano wrote:
> Hi Pádraig,
>
> I tried to reproduce your results but I wasn't able to do it. The
> biggest difference on a 300MB file I noticed was approximately 15% using
> on both implementations -O2, and 5% using -O3.
> My GCC version is "gcc (Debian 4.3.3-14) 4.3.3" and the CPU is: Intel(R)
> Pentium(R) D CPU 3.20GHz.
>
> I also spent some time trying to improve the gnulib SHA1 implementation
> and it seems a lookup table can improve things a bit.
>
> Can you please try the patch that I have attached and tell me which
> performance difference (if any) you get?
Thanks for looking at this Giuseppe
and sorry for not mentioning my GCC and CPU.
Note the binaries below is compiled with
$(rpm -q --qf="%{OPTFLAGS}\n" coreutils)
for consistency, which on my F11 machines is:
-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions
-fstack-protector --param=ssp-buffer-size=4 -m32 -march=i586
-mtune=generic -fasynchronous-unwind-tables -D_GNU_SOURCE=1
Testing on 2 machines I have here:
$ rpm -q gcc
gcc-4.4.1-2.fc11.i586
$ grep "model name" /proc/cpuinfo | head -n1 | tr -s '[:blank:]' ' '
model name : Intel(R) Pentium(R) M processor 1.70GHz
$ truncate -s300MB sha1.test
$ time sha1sum sha1.test
real 0m3.540s
$ time linus-sha1 sha1.test
real 0m2.319s (-34%)
$ time giuseppe-sha1sum sha1.test
real 0m3.513s (-.8%)
$ rpm -q gcc
gcc-4.4.1-2.fc11.i586
$ grep "model name" /proc/cpuinfo | head -n1 | tr -s '[:blank:]' ' '
model name : Intel(R) Core(TM) i7 CPU 920 @ 2.67GHz
$ truncate -s300MB sha1.test
$ time sha1sum sha1.test
real 0m1.857s
$ time linus-sha1 sha1.test
real 0m1.102s (-40%)
$ time giuseppe-sha1sum sha1.test
real 0m1.932s (+ 4%)
cheers,
Pádraig.
^ permalink raw reply
* Re: [PATCH 1/2] git_remote_cvs: Honor DESTDIR in the Makefile
From: Johan Herland @ 2009-08-17 1:58 UTC (permalink / raw)
To: David Aguilar; +Cc: Johannes Schindelin, gitster, git, barkalow, mhagger
In-Reply-To: <20090816210300.GB23522@gmail.com>
On Sunday 16 August 2009, David Aguilar wrote:
> I'll see if we rework this so that we end up passing "" to
> --root instead of /. I'm going to be gone for a few hours so
> probably won't be able to try it out until tonight.
Thanks a lot for your work! I will send an updated series shortly which will
include v2 of your DESTDIR/Makefile fixes, and also the fixes you suggested
earlier (including _lots_ of PEP8 fixes).
> Another thing to consider --
>
> Debian once submitted a bug against another Python app asking
> that we not place modules in site-packages unless we
> plan on having other applications importing those modules.
>
> The more appropriate place for them if we don't plan on that is
> $(prefix)/share/git-core/git_remote_cvs or something like that.
>
> I guess that's another thing to think about.
Yes, Debian raises a valid point. I haven't thought much about making the
git_remote_cvs package into something that would be useful for other
applications. (I just assumed that the Python convention was to install it
into site-packages regardless...) For now, I'll concentrate on git-remote-
cvs, and leave it to others to figure out if anything in the git_remote_cvs
package is useful for other programs.
Note that there's a small chicken-and-egg problem here as well: If Debian
refuses us to install into site-packages, it will be harder for other Python
programs to discover (and import) the git_remote_cvs package.
BTW, when we're on the subject of packaging: There are some variables in
git_remote_cvs/setup.py where I'm not sure what the correct value should be:
- version - should this follow Git's version number, or is it independent?
- author (and author_email + url) - For now, I'm referring to the Git
community. Should this be more specific/
Feedback welcome.
Have fun! :)
...Johan
--
Johan Herland, <johan@herland.net>
www.herland.net
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox