diff options
40 files changed, 1372 insertions, 358 deletions
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index a0a5ed0382..76b135508a 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -9,6 +9,7 @@ Bundle-RequiredExecutionEnvironment: J2SE-1.5 Import-Package: org.eclipse.jgit.api;version="[1.1.0,1.2.0)", org.eclipse.jgit.api.errors;version="[1.1.0,1.2.0)", org.eclipse.jgit.awtui;version="[1.1.0,1.2.0)", + org.eclipse.jgit.blame;version="[1.1.0,1.2.0)", org.eclipse.jgit.diff;version="[1.1.0,1.2.0)", org.eclipse.jgit.dircache;version="[1.1.0,1.2.0)", org.eclipse.jgit.errors;version="[1.1.0,1.2.0)", diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin index 1586528c6c..6562423f89 100644 --- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin +++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin @@ -1,5 +1,6 @@ org.eclipse.jgit.pgm.Add org.eclipse.jgit.pgm.AmazonS3Client +org.eclipse.jgit.pgm.Blame org.eclipse.jgit.pgm.Branch org.eclipse.jgit.pgm.Checkout org.eclipse.jgit.pgm.Clone diff --git a/org.eclipse.jgit.pgm/jgit.sh b/org.eclipse.jgit.pgm/jgit.sh index 9ff59d6122..f18e85fe45 100644 --- a/org.eclipse.jgit.pgm/jgit.sh +++ b/org.eclipse.jgit.pgm/jgit.sh @@ -52,6 +52,7 @@ done use_pager= case "$cmd" in +blame) use_pager=1 ;; diff) use_pager=1 ;; log) use_pager=1 ;; esac diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties index 98fbd7fbc4..1c95fd5f97 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties @@ -51,6 +51,7 @@ failedToLockTag=Failed to lock tag {0}: {1} fatalError=fatal: {0} fatalErrorTagExists=fatal: tag '{0}' exists fatalThisProgramWillDestroyTheRepository=fatal: This program will destroy the repository\nfatal:\nfatal:\nfatal: {0}\nfatal:\nfatal: To continue, add {1} to the command line\nfatal: +fileIsRequired=argument file is required forcedUpdate=forced update fromURI=From {0} initializedEmptyGitRepositoryIn=Initialized empty Git repository in {0} @@ -65,6 +66,8 @@ metaVar_KEY=KEY metaVar_arg=ARG metaVar_author=AUTHOR metaVar_base=base +metaVar_blameL=START,END +metaVar_blameReverse=START..END metaVar_bucket=BUCKET metaVar_command=command metaVar_commandDetail=DETAIL @@ -93,6 +96,7 @@ metaVar_ref=REF metaVar_refs=REFS metaVar_refspec=refspec metaVar_remoteName=name +metaVar_revision=REVISION metaVar_seconds=SECONDS metaVar_service=SERVICE metaVar_treeish=tree-ish @@ -132,6 +136,7 @@ timeInMilliSeconds={0} ms tooManyRefsGiven=Too many refs given unknownMergeStratey=unknown merge strategy {0} specified unsupportedOperation=Unsupported operation: {0} +usage_Blame=Show what revision and author last modified each line usage_CommandLineClientForamazonsS3Service=Command line client for Amazon's S3 service usage_CommitAuthor=Override the author name used in the commit. You can use the standard A U Thor <author@example.com> format. usage_CommitMessage=Use the given <msg> as the commit message @@ -152,11 +157,22 @@ usage_ServerSideBackendForJgitPush=Server side backend for 'jgit push' usage_ShowDiffs=Show diffs usage_StopTrackingAFile=Stop tracking a file usage_UpdateRemoteRepositoryFromLocalRefs=Update remote repository from local refs +usage_abbrevCommits=abbreviate commits to N + 1 digits usage_abortConnectionIfNoActivity=abort connection if no activity usage_actOnRemoteTrackingBranches=act on remote-tracking branches usage_addFileContentsToTheIndex=Add file contents to the index usage_alterTheDetailShown=alter the detail shown usage_approveDestructionOfRepository=approve destruction of repository +usage_blameLongRevision=show long revision +usage_blameRange=annotate only the given range +usage_blameRawTimestamp=show raw timestamp +usage_blameReverse=show origin of deletions instead of insertions +usage_blameShowBlankBoundary=show blank SHA-1 for boundary commits +usage_blameShowEmail=show author email instead of name +usage_blameShowRoot=do not treat root commits as boundaries +usage_blameShowSourceLine=show source line number +usage_blameShowSourcePath=show source filename +usage_blameSuppressAuthor=do not show author name and timestamp usage_beMoreVerbose=be more verbose usage_beVerbose=be verbose usage_cached=compare against index @@ -188,6 +204,7 @@ usage_forceCreateBranchEvenExists=force create branch even exists usage_forceReplacingAnExistingTag=force replacing an existing tag usage_hostnameOrIpToListenOn=hostname (or ip) to listen on usage_indexFileFormatToCreate=index file format to create +usage_ignoreWhitespace=ignore all whitespace usage_inputOutputFile=Input/output file usage_listBothRemoteTrackingAndLocalBranches=list both remote-tracking and local branches usage_listCreateOrDeleteBranches=List, create, or delete branches diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java new file mode 100644 index 0000000000..162f433ffa --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Blame.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2011, Google Inc. + * Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com> + * Copyright (C) 2009, Johannes E. Schindelin + * Copyright (C) 2009, Johannes Schindelin <johannes.schindelin@gmx.de> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.eclipse.jgit.blame.BlameGenerator; +import org.eclipse.jgit.blame.BlameResult; +import org.eclipse.jgit.diff.RawText; +import org.eclipse.jgit.diff.RawTextComparator; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevFlag; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +@Command(common = false, usage = "usage_Blame") +class Blame extends TextBuiltin { + private RawTextComparator comparator = RawTextComparator.DEFAULT; + + @Option(name = "-w", usage = "usage_ignoreWhitespace") + void ignoreAllSpace(@SuppressWarnings("unused") boolean on) { + comparator = RawTextComparator.WS_IGNORE_ALL; + } + + @Option(name = "--abbrev", metaVar = "metaVar_n", usage = "usage_abbrevCommits") + private int abbrev; + + @Option(name = "-l", usage = "usage_blameLongRevision") + private boolean showLongRevision; + + @Option(name = "-t", usage = "usage_blameRawTimestamp") + private boolean showRawTimestamp; + + @Option(name = "-b", usage = "usage_blameShowBlankBoundary") + private boolean showBlankBoundary; + + @Option(name = "-s", usage = "usage_blameSuppressAuthor") + private boolean noAuthor; + + @Option(name = "--show-email", aliases = { "-e" }, usage = "usage_blameShowEmail") + private boolean showAuthorEmail; + + @Option(name = "--show-name", aliases = { "-f" }, usage = "usage_blameShowSourcePath") + private boolean showSourcePath; + + @Option(name = "--show-number", aliases = { "-n" }, usage = "usage_blameShowSourceLine") + private boolean showSourceLine; + + @Option(name = "--root", usage = "usage_blameShowRoot") + private boolean root; + + @Option(name = "-L", metaVar = "metaVar_blameL", usage = "usage_blameRange") + private String rangeString; + + @Option(name = "--reverse", metaVar = "metaVar_blameReverse", usage = "usage_blameReverse") + private List<RevCommit> reverseRange = new ArrayList<RevCommit>(2); + + @Argument(index = 0, required = false, metaVar = "metaVar_revision") + private String revision; + + @Argument(index = 1, required = false, metaVar = "metaVar_file") + private String file; + + private ObjectReader reader; + + private final Map<RevCommit, String> abbreviatedCommits = new HashMap<RevCommit, String>(); + + private SimpleDateFormat dateFmt; + + private int begin; + + private int end; + + private BlameResult blame; + + @Override + protected void run() throws Exception { + if (file == null) { + if (revision == null) + throw die(CLIText.get().fileIsRequired); + file = revision; + revision = null; + } + + if (abbrev == 0) + abbrev = db.getConfig().getInt("core", "abbrev", 7); + if (!showBlankBoundary) + root = db.getConfig().getBoolean("blame", "blankboundary", false); + if (!root) + root = db.getConfig().getBoolean("blame", "showroot", false); + + if (showRawTimestamp) + dateFmt = new SimpleDateFormat("ZZZZ"); + else + dateFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ZZZZ"); + + BlameGenerator generator = new BlameGenerator(db, file); + reader = db.newObjectReader(); + try { + generator.setTextComparator(comparator); + + if (!reverseRange.isEmpty()) { + RevCommit rangeStart = null; + List<RevCommit> rangeEnd = new ArrayList<RevCommit>(2); + for (RevCommit c : reverseRange) { + if (c.has(RevFlag.UNINTERESTING)) + rangeStart = c; + else + rangeEnd.add(c); + } + generator.reverse(rangeStart, rangeEnd); + } else if (revision != null) { + generator.push(null, db.resolve(revision + "^{commit}")); + } else { + generator.push(null, db.resolve(Constants.HEAD)); + if (!db.isBare()) { + DirCache dc = db.readDirCache(); + int entry = dc.findEntry(file); + if (0 <= entry) + generator.push(null, dc.getEntry(entry).getObjectId()); + + File inTree = new File(db.getWorkTree(), file); + if (inTree.isFile()) + generator.push(null, new RawText(inTree)); + } + } + + blame = BlameResult.create(generator); + begin = 0; + end = blame.getResultContents().size(); + if (rangeString != null) + parseLineRangeOption(); + blame.computeRange(begin, end); + + int authorWidth = 8; + int dateWidth = 8; + int pathWidth = 1; + int maxSourceLine = 1; + for (int line = begin; line < end; line++) { + authorWidth = Math.max(authorWidth, author(line).length()); + dateWidth = Math.max(dateWidth, date(line).length()); + pathWidth = Math.max(pathWidth, path(line).length()); + maxSourceLine = Math.max(maxSourceLine, blame.getSourceLine(line)); + } + + String pathFmt = MessageFormat.format(" %{0}s", pathWidth); + String numFmt = MessageFormat.format(" %{0}d", + 1 + (int) Math.log10(maxSourceLine + 1)); + String lineFmt = MessageFormat.format(" %{0}d) ", + 1 + (int) Math.log10(end + 1)); + String authorFmt = MessageFormat.format(" (%-{0}s %{1}s", + authorWidth, dateWidth); + + for (int line = begin; line < end; line++) { + out.print(abbreviate(blame.getSourceCommit(line))); + if (showSourcePath) + out.format(pathFmt, path(line)); + if (showSourceLine) + out.format(numFmt, blame.getSourceLine(line) + 1); + if (!noAuthor) + out.format(authorFmt, author(line), date(line)); + out.format(lineFmt, line + 1); + out.flush(); + blame.getResultContents().writeLine(System.out, line); + out.print('\n'); + } + } finally { + generator.release(); + reader.release(); + } + } + + private void parseLineRangeOption() { + String beginStr, endStr; + if (rangeString.startsWith("/")) { + int c = rangeString.indexOf("/,", 1); + if (c < 0) { + beginStr = rangeString; + endStr = String.valueOf(end); + } else { + beginStr = rangeString.substring(0, c); + endStr = rangeString.substring(c + 2); + } + + } else { + int c = rangeString.indexOf(','); + if (c < 0) { + beginStr = rangeString; + endStr = String.valueOf(end); + } else if (c == 0) { + beginStr = "0"; + endStr = rangeString.substring(1); + } else { + beginStr = rangeString.substring(0, c); + endStr = rangeString.substring(c + 1); + } + } + + if (beginStr.equals("")) + begin = 0; + else if (beginStr.startsWith("/")) + begin = findLine(0, beginStr); + else + begin = Math.max(0, Integer.parseInt(beginStr) - 1); + + if (endStr.equals("")) + end = blame.getResultContents().size(); + else if (endStr.startsWith("/")) + end = findLine(begin, endStr); + else if (endStr.startsWith("-")) + end = begin + Integer.parseInt(endStr); + else if (endStr.startsWith("+")) + end = begin + Integer.parseInt(endStr.substring(1)); + else + end = Math.max(0, Integer.parseInt(endStr) - 1); + } + + private int findLine(int b, String regex) { + String re = regex.substring(1, regex.length() - 1); + if (!re.startsWith("^")) + re = ".*" + re; + if (!re.endsWith("$")) + re = re + ".*"; + Pattern p = Pattern.compile(re); + RawText text = blame.getResultContents(); + for (int line = b; line < text.size(); line++) { + if (p.matcher(text.getString(line)).matches()) + return line; + } + return b; + } + + private String path(int line) { + String p = blame.getSourcePath(line); + return p != null ? p : ""; + } + + private String author(int line) { + PersonIdent author = blame.getSourceAuthor(line); + if (author == null) + return ""; + String name = showAuthorEmail ? author.getEmailAddress() : author + .getName(); + return name != null ? name : ""; + } + + private String date(int line) { + if (blame.getSourceCommit(line) == null) + return ""; + + PersonIdent author = blame.getSourceAuthor(line); + if (author == null) + return ""; + + dateFmt.setTimeZone(author.getTimeZone()); + if (!showRawTimestamp) + return dateFmt.format(author.getWhen()); + return String.format("%d %s", author.getWhen().getTime() / 1000L, + dateFmt.format(author.getWhen())); + } + + private String abbreviate(RevCommit commit) throws IOException { + String r = abbreviatedCommits.get(commit); + if (r != null) + return r; + + if (showBlankBoundary && commit.getParentCount() == 0) + commit = null; + + if (commit == null) { + int len = showLongRevision ? OBJECT_ID_STRING_LENGTH : (abbrev + 1); + StringBuilder b = new StringBuilder(len); + for (int i = 0; i < len; i++) + b.append(' '); + r = b.toString(); + + } else if (!root && commit.getParentCount() == 0) { + if (showLongRevision) + r = "^" + commit.name().substring(0, OBJECT_ID_STRING_LENGTH - 1); + else + r = "^" + reader.abbreviate(commit, abbrev).name(); + } else { + if (showLongRevision) + r = commit.name(); + else + r = reader.abbreviate(commit, abbrev + 1).name(); + } + + abbreviatedCommits.put(commit, r); + return r; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java index d82ff499f9..e1c26adf4c 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CLIText.java @@ -104,6 +104,7 @@ public class CLIText extends TranslationBundle { /***/ public String fatalError; /***/ public String fatalErrorTagExists; /***/ public String fatalThisProgramWillDestroyTheRepository; + /***/ public String fileIsRequired; /***/ public String forcedUpdate; /***/ public String fromURI; /***/ public String initializedEmptyGitRepositoryIn; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java index 474e974cd1..715cb71b43 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java @@ -199,9 +199,11 @@ abstract class RevWalkTextBuiltin extends TextBuiltin { } protected RevWalk createWalk() { - if (argWalk == null) - argWalk = objects ? new ObjectWalk(db) : new RevWalk(db); - return argWalk; + if (objects) + return new ObjectWalk(db); + if (argWalk != null) + return argWalk; + return new RevWalk(db); } protected int walkLoop() throws Exception { diff --git a/org.eclipse.jgit.storage.dht.test/tst/org/eclipse/jgit/storage/dht/ChunkKeyTest.java b/org.eclipse.jgit.storage.dht.test/tst/org/eclipse/jgit/storage/dht/ChunkKeyTest.java index 63cbf520c0..9219663667 100644 --- a/org.eclipse.jgit.storage.dht.test/tst/org/eclipse/jgit/storage/dht/ChunkKeyTest.java +++ b/org.eclipse.jgit.storage.dht.test/tst/org/eclipse/jgit/storage/dht/ChunkKeyTest.java @@ -43,7 +43,8 @@ package org.eclipse.jgit.storage.dht; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import org.eclipse.jgit.lib.ObjectId; import org.junit.Test; @@ -59,19 +60,19 @@ public class ChunkKeyTest { ChunkKey key1 = ChunkKey.create(repo1, id); assertEquals(repo1.asInt(), key1.getRepositoryId()); assertEquals(id, key1.getChunkHash()); - assertEquals("3e.41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", + assertEquals("41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", key1.asString()); ChunkKey key2 = ChunkKey.fromBytes(key1.asBytes()); assertEquals(repo1.asInt(), key2.getRepositoryId()); assertEquals(id, key2.getChunkHash()); - assertEquals("3e.41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", + assertEquals("41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", key2.asString()); ChunkKey key3 = ChunkKey.fromString(key1.asString()); assertEquals(repo1.asInt(), key3.getRepositoryId()); assertEquals(id, key3.getChunkHash()); - assertEquals("3e.41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", + assertEquals("41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", key3.asString()); assertEquals(key1, key2); diff --git a/org.eclipse.jgit.storage.dht.test/tst/org/eclipse/jgit/storage/dht/ObjectIndexKeyTest.java b/org.eclipse.jgit.storage.dht.test/tst/org/eclipse/jgit/storage/dht/ObjectIndexKeyTest.java index ab3b423ede..d3419bd140 100644 --- a/org.eclipse.jgit.storage.dht.test/tst/org/eclipse/jgit/storage/dht/ObjectIndexKeyTest.java +++ b/org.eclipse.jgit.storage.dht.test/tst/org/eclipse/jgit/storage/dht/ObjectIndexKeyTest.java @@ -58,19 +58,19 @@ public class ObjectIndexKeyTest { ObjectIndexKey key1 = ObjectIndexKey.create(repo, id); assertEquals(repo.asInt(), key1.getRepositoryId()); assertEquals(key1, id); - assertEquals("3e.41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", + assertEquals("41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", key1.asString()); ObjectIndexKey key2 = ObjectIndexKey.fromBytes(key1.asBytes()); assertEquals(repo.asInt(), key2.getRepositoryId()); assertEquals(key2, id); - assertEquals("3e.41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", + assertEquals("41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", key2.asString()); ObjectIndexKey key3 = ObjectIndexKey.fromString(key1.asString()); assertEquals(repo.asInt(), key3.getRepositoryId()); assertEquals(key3, id); - assertEquals("3e.41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", + assertEquals("41234567.3e64b928d51b3a28e89cfe2a3f0eeae35ef07839", key3.asString()); } } diff --git a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/ChunkKey.java b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/ChunkKey.java index 272b5ea173..11a151f6ba 100644 --- a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/ChunkKey.java +++ b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/ChunkKey.java @@ -54,7 +54,7 @@ import org.eclipse.jgit.lib.ObjectId; /** Unique identifier of a {@link PackChunk} in the DHT. */ public final class ChunkKey implements RowKey { - static final int KEYLEN = 52; + static final int KEYLEN = 49; /** * @param repo @@ -84,8 +84,8 @@ public final class ChunkKey implements RowKey { throw new IllegalArgumentException(MessageFormat.format( DhtText.get().invalidChunkKey, decode(key, ptr, ptr + len))); - int repo = parse32(key, ptr + 3); - ObjectId chunk = ObjectId.fromString(key, ptr + 12); + int repo = parse32(key, ptr); + ObjectId chunk = ObjectId.fromString(key, ptr + 9); return new ChunkKey(repo, chunk); } @@ -122,13 +122,9 @@ public final class ChunkKey implements RowKey { public byte[] asBytes() { byte[] r = new byte[KEYLEN]; - chunk.copyTo(r, 12); - format32(r, 3, repo); - // bucket is the leading 2 digits of the SHA-1. - r[11] = '.'; - r[2] = '.'; - r[1] = r[12 + 1]; - r[0] = r[12 + 0]; + format32(r, 0, repo); + r[8] = '.'; + chunk.copyTo(r, 9); return r; } diff --git a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtCachedPack.java b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtCachedPack.java index 39a76463fb..0fd253bfbf 100644 --- a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtCachedPack.java +++ b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtCachedPack.java @@ -125,9 +125,18 @@ public class DhtCachedPack extends CachedPack { throws IOException { if (keyList == null) init(); - Prefetcher p = new Prefetcher(ctx, 0); - p.push(Arrays.asList(keyList)); - copyPack(out, p, validate); + + // Clear the recent chunks because all of the reader's + // chunk limit should be made available for prefetch. + int cacheLimit = ctx.getOptions().getChunkLimit(); + ctx.getRecentChunks().setMaxBytes(0); + try { + Prefetcher p = new Prefetcher(ctx, 0, cacheLimit); + p.push(Arrays.asList(keyList)); + copyPack(out, p, validate); + } finally { + ctx.getRecentChunks().setMaxBytes(cacheLimit); + } } private void copyPack(PackOutputStream out, Prefetcher prefetcher, diff --git a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtReader.java b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtReader.java index f9288b9e2e..330b5c0734 100644 --- a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtReader.java +++ b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtReader.java @@ -156,6 +156,10 @@ public class DhtReader extends ObjectReader implements ObjectReuseAsIs { return recentInfo; } + RecentChunks getRecentChunks() { + return recentChunks; + } + DeltaBaseCache getDeltaBaseCache() { return deltaBaseCache; } @@ -242,7 +246,7 @@ public class DhtReader extends ObjectReader implements ObjectReuseAsIs { // configured as push might invoke our own methods that may // try to call back into the active prefetcher. // - Prefetcher p = new Prefetcher(this, OBJ_COMMIT); + Prefetcher p = prefetch(OBJ_COMMIT, readerOptions.getWalkCommitsPrefetchRatio()); p.push(this, roots); prefetcher = p; } @@ -256,7 +260,7 @@ public class DhtReader extends ObjectReader implements ObjectReuseAsIs { // configured as push might invoke our own methods that may // try to call back into the active prefetcher. // - Prefetcher p = new Prefetcher(this, OBJ_TREE); + Prefetcher p = prefetch(OBJ_TREE, readerOptions.getWalkTreesPrefetchRatio()); p.push(this, min.getTree(), max.getTree()); prefetcher = p; } @@ -391,14 +395,22 @@ public class DhtReader extends ObjectReader implements ObjectReuseAsIs { new RepresentationSelector(packer, this, monitor).select(itr); } + private Prefetcher prefetch(final int type, final int ratio) { + int limit = readerOptions.getChunkLimit(); + int prefetchLimit = (int) (limit * (ratio / 100.0)); + recentChunks.setMaxBytes(limit - prefetchLimit); + return new Prefetcher(this, type, prefetchLimit); + } + private void endPrefetch() { + recentChunks.setMaxBytes(getOptions().getChunkLimit()); prefetcher = null; } @SuppressWarnings("unchecked") public void writeObjects(PackOutputStream out, List<ObjectToPack> objects) throws IOException { - prefetcher = new Prefetcher(this, 0); + prefetcher = prefetch(0, readerOptions.getWriteObjectsPrefetchRatio()); try { List itr = objects; new ObjectWriter(this, prefetcher).plan(itr); diff --git a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtReaderOptions.java b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtReaderOptions.java index 0890e39ad0..db3f51028f 100644 --- a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtReaderOptions.java +++ b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/DhtReaderOptions.java @@ -57,7 +57,15 @@ public class DhtReaderOptions { private boolean prefetchFollowEdgeHints; - private int prefetchLimit; + private int chunkLimit; + + private int openQueuePrefetchRatio; + + private int walkCommitsPrefetchRatio; + + private int walkTreesPrefetchRatio; + + private int writeObjectsPrefetchRatio; private int objectIndexConcurrentBatches; @@ -69,15 +77,18 @@ public class DhtReaderOptions { private int recentInfoCacheSize; - private int recentChunkCacheSize; - private boolean trackFirstChunkLoad; /** Create a default reader configuration. */ public DhtReaderOptions() { setTimeout(Timeout.seconds(5)); setPrefetchFollowEdgeHints(true); - setPrefetchLimit(5 * MiB); + + setChunkLimit(5 * MiB); + setOpenQueuePrefetchRatio(20 /* percent */); + setWalkCommitsPrefetchRatio(20 /* percent */); + setWalkTreesPrefetchRatio(20 /* percent */); + setWriteObjectsPrefetchRatio(90 /* percent */); setObjectIndexConcurrentBatches(2); setObjectIndexBatchSize(512); @@ -86,7 +97,6 @@ public class DhtReaderOptions { setDeltaBaseCacheLimit(10 * MiB); setRecentInfoCacheSize(4096); - setRecentChunkCacheSize(4); } /** @return default timeout to wait on long operations before aborting. */ @@ -125,19 +135,83 @@ public class DhtReaderOptions { return this; } - /** @return number of bytes to load during prefetching. */ - public int getPrefetchLimit() { - return prefetchLimit; + /** @return number of bytes to hold within a DhtReader. */ + public int getChunkLimit() { + return chunkLimit; } /** - * Set the number of bytes the prefetcher should hold onto. + * Set the number of bytes hold within a DhtReader. * * @param maxBytes * @return {@code this} */ - public DhtReaderOptions setPrefetchLimit(int maxBytes) { - prefetchLimit = Math.max(1024, maxBytes); + public DhtReaderOptions setChunkLimit(int maxBytes) { + chunkLimit = Math.max(1024, maxBytes); + return this; + } + + /** @return percentage of {@link #getChunkLimit()} used for prefetch, 0..100. */ + public int getOpenQueuePrefetchRatio() { + return openQueuePrefetchRatio; + } + + /** + * Set the prefetch ratio used by the open object queue. + * + * @param ratio 0..100. + * @return {@code this} + */ + public DhtReaderOptions setOpenQueuePrefetchRatio(int ratio) { + openQueuePrefetchRatio = Math.max(0, Math.min(ratio, 100)); + return this; + } + + /** @return percentage of {@link #getChunkLimit()} used for prefetch, 0..100. */ + public int getWalkCommitsPrefetchRatio() { + return walkCommitsPrefetchRatio; + } + + /** + * Set the prefetch ratio used by the open object queue. + * + * @param ratio 0..100. + * @return {@code this} + */ + public DhtReaderOptions setWalkCommitsPrefetchRatio(int ratio) { + walkCommitsPrefetchRatio = Math.max(0, Math.min(ratio, 100)); + return this; + } + + /** @return percentage of {@link #getChunkLimit()} used for prefetch, 0..100. */ + public int getWalkTreesPrefetchRatio() { + return walkTreesPrefetchRatio; + } + + /** + * Set the prefetch ratio used by the open object queue. + * + * @param ratio 0..100. + * @return {@code this} + */ + public DhtReaderOptions setWalkTreesPrefetchRatio(int ratio) { + walkTreesPrefetchRatio = Math.max(0, Math.min(ratio, 100)); + return this; + } + + /** @return percentage of {@link #getChunkLimit()} used for prefetch, 0..100. */ + public int getWriteObjectsPrefetchRatio() { + return writeObjectsPrefetchRatio; + } + + /** + * Set the prefetch ratio used by the open object queue. + * + * @param ratio 0..100. + * @return {@code this} + */ + public DhtReaderOptions setWriteObjectsPrefetchRatio(int ratio) { + writeObjectsPrefetchRatio = Math.max(0, Math.min(ratio, 100)); return this; } @@ -226,24 +300,6 @@ public class DhtReaderOptions { return this; } - /** @return number of recent chunks to hold onto per-reader. */ - public int getRecentChunkCacheSize() { - return recentChunkCacheSize; - } - - /** - * Set the number of chunks each reader holds onto for recently used access. - * - * @param chunkCnt - * the number of chunks each reader retains of recently used - * chunks to smooth out access. - * @return {@code this} - */ - public DhtReaderOptions setRecentChunkCacheSize(int chunkCnt) { - recentChunkCacheSize = Math.max(0, chunkCnt); - return this; - } - /** * @return true if {@link DhtReader.Statistics} includes the stack trace for * the first time a chunk is loaded. Supports debugging DHT code. @@ -277,7 +333,11 @@ public class DhtReaderOptions { public DhtReaderOptions fromConfig(Config rc) { setTimeout(Timeout.getTimeout(rc, "core", "dht", "timeout", getTimeout())); setPrefetchFollowEdgeHints(rc.getBoolean("core", "dht", "prefetchFollowEdgeHints", isPrefetchFollowEdgeHints())); - setPrefetchLimit(rc.getInt("core", "dht", "prefetchLimit", getPrefetchLimit())); + setChunkLimit(rc.getInt("core", "dht", "chunkLimit", getChunkLimit())); + setOpenQueuePrefetchRatio(rc.getInt("core", "dht", "openQueuePrefetchRatio", getOpenQueuePrefetchRatio())); + setWalkCommitsPrefetchRatio(rc.getInt("core", "dht", "walkCommitsPrefetchRatio", getWalkCommitsPrefetchRatio())); + setWalkTreesPrefetchRatio(rc.getInt("core", "dht", "walkTreesPrefetchRatio", getWalkTreesPrefetchRatio())); + setWriteObjectsPrefetchRatio(rc.getInt("core", "dht", "writeObjectsPrefetchRatio", getWriteObjectsPrefetchRatio())); setObjectIndexConcurrentBatches(rc.getInt("core", "dht", "objectIndexConcurrentBatches", getObjectIndexConcurrentBatches())); setObjectIndexBatchSize(rc.getInt("core", "dht", "objectIndexBatchSize", getObjectIndexBatchSize())); @@ -286,7 +346,6 @@ public class DhtReaderOptions { setDeltaBaseCacheLimit(rc.getInt("core", "dht", "deltaBaseCacheLimit", getDeltaBaseCacheLimit())); setRecentInfoCacheSize(rc.getInt("core", "dht", "recentInfoCacheSize", getRecentInfoCacheSize())); - setRecentChunkCacheSize(rc.getInt("core", "dht", "recentChunkCacheSize", getRecentChunkCacheSize())); setTrackFirstChunkLoad(rc.getBoolean("core", "dht", "debugTrackFirstChunkLoad", isTrackFirstChunkLoad())); return this; diff --git a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/ObjectIndexKey.java b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/ObjectIndexKey.java index b38fdcec22..ab8f8352b0 100644 --- a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/ObjectIndexKey.java +++ b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/ObjectIndexKey.java @@ -55,7 +55,7 @@ import org.eclipse.jgit.lib.ObjectId; /** Identifies an ObjectId in the DHT. */ public final class ObjectIndexKey extends ObjectId implements RowKey { - private static final int KEYLEN = 52; + private static final int KEYLEN = 49; /** * @param repo @@ -75,8 +75,8 @@ public final class ObjectIndexKey extends ObjectId implements RowKey { throw new IllegalArgumentException(MessageFormat.format( DhtText.get().invalidChunkKey, decode(key))); - int repo = parse32(key, 3); - ObjectId id = ObjectId.fromString(key, 12); + int repo = parse32(key, 0); + ObjectId id = ObjectId.fromString(key, 9); return new ObjectIndexKey(repo, id); } @@ -106,13 +106,9 @@ public final class ObjectIndexKey extends ObjectId implements RowKey { public byte[] asBytes() { byte[] r = new byte[KEYLEN]; - copyTo(r, 12); - format32(r, 3, repo); - // bucket is the leading 2 digits of the SHA-1. - r[11] = '.'; - r[2] = '.'; - r[1] = r[12 + 1]; - r[0] = r[12 + 0]; + format32(r, 0, repo); + r[8] = '.'; + copyTo(r, 9); return r; } diff --git a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/OpenQueue.java b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/OpenQueue.java index e47f2b2cb4..32b22340d1 100644 --- a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/OpenQueue.java +++ b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/OpenQueue.java @@ -159,6 +159,7 @@ final class OpenQueue<T extends ObjectId> extends QueueObjectLookup<T> @Override public void release() { + reader.getRecentChunks().setMaxBytes(reader.getOptions().getChunkLimit()); prefetcher = null; currChunk = null; } @@ -173,8 +174,13 @@ final class OpenQueue<T extends ObjectId> extends QueueObjectLookup<T> list = new ArrayList<ObjectWithInfo<T>>(); byChunk.put(chunkKey, list); - if (prefetcher == null) - prefetcher = new Prefetcher(reader, 0); + if (prefetcher == null) { + int limit = reader.getOptions().getChunkLimit(); + int ratio = reader.getOptions().getOpenQueuePrefetchRatio(); + int prefetchLimit = (int) (limit * (ratio / 100.0)); + reader.getRecentChunks().setMaxBytes(limit - prefetchLimit); + prefetcher = new Prefetcher(reader, 0, prefetchLimit); + } prefetcher.push(chunkKey); } list.add(c); diff --git a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/PackChunk.java b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/PackChunk.java index c0684022f3..66d3d3386b 100644 --- a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/PackChunk.java +++ b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/PackChunk.java @@ -291,7 +291,7 @@ public final class PackChunk { } int findOffset(RepositoryKey repo, AnyObjectId objId) { - if (key.getRepositoryId() == repo.asInt()) + if (key.getRepositoryId() == repo.asInt() && index != null) return index.findOffset(objId); return -1; } diff --git a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/Prefetcher.java b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/Prefetcher.java index 743f1f5944..fef2b4f29d 100644 --- a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/Prefetcher.java +++ b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/Prefetcher.java @@ -104,7 +104,7 @@ class Prefetcher implements StreamingCallback<Collection<PackChunk.Members>> { private DhtException error; - Prefetcher(DhtReader reader, int objectType) { + Prefetcher(DhtReader reader, int objectType, int prefetchLimitInBytes) { this.db = reader.getDatabase(); this.stats = reader.getStatistics(); this.objectType = objectType; @@ -113,7 +113,7 @@ class Prefetcher implements StreamingCallback<Collection<PackChunk.Members>> { this.queue = new LinkedList<ChunkKey>(); this.followEdgeHints = reader.getOptions().isPrefetchFollowEdgeHints(); this.averageChunkSize = reader.getInserterOptions().getChunkSize(); - this.highWaterMark = reader.getOptions().getPrefetchLimit(); + this.highWaterMark = prefetchLimitInBytes; int lwm = (highWaterMark / averageChunkSize) - 4; if (lwm <= 0) diff --git a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/RecentChunks.java b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/RecentChunks.java index f75e3bdc82..22608ee1b3 100644 --- a/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/RecentChunks.java +++ b/org.eclipse.jgit.storage.dht/src/org/eclipse/jgit/storage/dht/RecentChunks.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.storage.dht; import java.io.IOException; +import java.util.HashMap; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectLoader; @@ -55,9 +56,11 @@ final class RecentChunks { private final DhtReader.Statistics stats; - private final int maxSize; + private final HashMap<ChunkKey, Node> byKey; - private int curSize; + private int maxBytes; + + private int curBytes; private Node lruHead; @@ -66,38 +69,56 @@ final class RecentChunks { RecentChunks(DhtReader reader) { this.reader = reader; this.stats = reader.getStatistics(); - this.maxSize = reader.getOptions().getRecentChunkCacheSize(); + this.byKey = new HashMap<ChunkKey, Node>(); + this.maxBytes = reader.getOptions().getChunkLimit(); + } + + void setMaxBytes(int newMax) { + maxBytes = Math.max(0, newMax); + if (0 < maxBytes) + prune(); + else + clear(); } PackChunk get(ChunkKey key) { - for (Node n = lruHead; n != null; n = n.next) { - if (key.equals(n.chunk.getChunkKey())) { - hit(n); - stats.recentChunks_Hits++; - return n.chunk; - } + Node n = byKey.get(key); + if (n != null) { + hit(n); + stats.recentChunks_Hits++; + return n.chunk; } stats.recentChunks_Miss++; return null; } void put(PackChunk chunk) { - for (Node n = lruHead; n != null; n = n.next) { - if (n.chunk == chunk) { - hit(n); - return; - } + Node n = byKey.get(chunk.getChunkKey()); + if (n != null && n.chunk == chunk) { + hit(n); + return; } - Node n; - if (curSize < maxSize) { - n = new Node(); - curSize++; - } else { - n = lruTail; - } + curBytes += chunk.getTotalSize(); + prune(); + + n = new Node(); n.chunk = chunk; - hit(n); + byKey.put(chunk.getChunkKey(), n); + first(n); + } + + private void prune() { + while (maxBytes < curBytes) { + Node n = lruTail; + if (n == null) + break; + + PackChunk c = n.chunk; + curBytes -= c.getTotalSize(); + byKey.remove(c.getChunkKey()); + remove(n); + } } ObjectLoader open(RepositoryKey repo, AnyObjectId objId, int typeHint) @@ -164,9 +185,10 @@ final class RecentChunks { } void clear() { - curSize = 0; + curBytes = 0; lruHead = null; lruTail = null; + byKey.clear(); } private void hit(Node n) { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java index dc2ccb97fb..3ca4f589db 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/storage/file/RefDirectoryTest.java @@ -61,6 +61,9 @@ import java.io.File; import java.io.IOException; import java.util.Map; +import org.eclipse.jgit.events.ListenerHandle; +import org.eclipse.jgit.events.RefsChangedEvent; +import org.eclipse.jgit.events.RefsChangedListener; import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.AnyObjectId; @@ -466,6 +469,33 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase { } @Test + public void testGetRefs_LooseSorting_Bug_348834() throws IOException { + Map<String, Ref> refs; + + writeLooseRef("refs/heads/my/a+b", A); + writeLooseRef("refs/heads/my/a/b/c", B); + + final int[] count = new int[1]; + + ListenerHandle listener = Repository.getGlobalListenerList() + .addRefsChangedListener(new RefsChangedListener() { + + public void onRefsChanged(RefsChangedEvent event) { + count[0]++; + } + }); + + refs = refdir.getRefs(RefDatabase.ALL); + refs = refdir.getRefs(RefDatabase.ALL); + listener.remove(); + assertEquals(1, count[0]); // Bug 348834 multiple RefsChangedEvents + assertEquals(2, refs.size()); + assertEquals(A, refs.get("refs/heads/my/a+b").getObjectId()); + assertEquals(B, refs.get("refs/heads/my/a/b/c").getObjectId()); + + } + + @Test public void testGetRefs_TagsOnly_AllPacked() throws IOException { Map<String, Ref> tags; Ref a; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java index f3a2e53602..8caff06292 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -66,6 +66,7 @@ import org.eclipse.jgit.errors.CheckoutConflictException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate.Result; @@ -266,9 +267,14 @@ public class CheckoutCommand extends GitCommand<Ref> { } File workTree = repo.getWorkTree(); - for (String file : files) - DirCacheCheckout.checkoutEntry(repo, new File(workTree, file), - dc.getEntry(file)); + ObjectReader r = repo.getObjectDatabase().newReader(); + try { + for (String file : files) + DirCacheCheckout.checkoutEntry(repo, new File(workTree, + file), dc.getEntry(file), r); + } finally { + r.release(); + } } finally { dc.unlock(); revWalk.release(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java index cfbcbf1b6d..29dd6e90c3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java @@ -301,8 +301,13 @@ public class CloneCommand implements Callable<Git> { } /** + * The remote name used to keep track of the upstream repository for the + * clone operation. If no remote name is set, the default value of + * <code>Constants.DEFAULT_REMOTE_NAME</code> will be used. + * + * @see Constants#DEFAULT_REMOTE_NAME * @param remote - * the branch to keep track of in the origin repository + * name that keeps track of the upstream repository * @return this instance */ public CloneCommand setRemote(String remote) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java index 1ec8685f7e..86e4e78c8c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java @@ -70,37 +70,72 @@ public class MergeResult { public String toString() { return "Fast-forward"; } + + @Override + public boolean isSuccessful() { + return true; + } }, /** */ ALREADY_UP_TO_DATE { public String toString() { return "Already-up-to-date"; } + + @Override + public boolean isSuccessful() { + return true; + } }, /** */ FAILED { public String toString() { return "Failed"; } + + @Override + public boolean isSuccessful() { + return false; + } }, /** */ MERGED { public String toString() { return "Merged"; } + + @Override + public boolean isSuccessful() { + return true; + } }, /** */ CONFLICTING { public String toString() { return "Conflicting"; } + + @Override + public boolean isSuccessful() { + return false; + } }, /** */ NOT_SUPPORTED { public String toString() { return "Not-yet-supported"; } - } + + @Override + public boolean isSuccessful() { + return false; + } + }; + + /** + * @return whether the status indicates a successful result + */ + public abstract boolean isSuccessful(); } private ObjectId[] mergedCommits; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java index 8eae4027a1..25dd52822a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java @@ -194,7 +194,8 @@ public class PullCommand extends GitCommand<PullResult> { String remoteUri; FetchResult fetchRes; if (isRemote) { - remoteUri = repoConfig.getString("remote", remote, + remoteUri = repoConfig.getString( + ConfigConstants.CONFIG_REMOTE_SECTION, remote, ConfigConstants.CONFIG_KEY_URL); if (remoteUri == null) { String missingKey = ConfigConstants.CONFIG_REMOTE_SECTION + DOT diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java index 40ed137ee0..49327fc9b9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java @@ -101,6 +101,17 @@ public class PullResult { return this.fetchedFrom; } + /** + * @return whether the pull was successful + */ + public boolean isSuccessful() { + if (mergeResult != null) + return mergeResult.getMergeStatus().isSuccessful(); + else if (rebaseResult != null) + return rebaseResult.getStatus().isSuccessful(); + return true; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java index af070d6535..07cd8eb20a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java @@ -59,27 +59,62 @@ public class RebaseResult { /** * Rebase was successful, HEAD points to the new commit */ - OK, + OK { + @Override + public boolean isSuccessful() { + return true; + } + }, /** * Aborted; the original HEAD was restored */ - ABORTED, + ABORTED { + @Override + public boolean isSuccessful() { + return false; + } + }, /** * Stopped due to a conflict; must either abort or resolve or skip */ - STOPPED, + STOPPED { + @Override + public boolean isSuccessful() { + return false; + } + }, /** * Failed; the original HEAD was restored */ - FAILED, + FAILED { + @Override + public boolean isSuccessful() { + return false; + } + }, /** * Already up-to-date */ - UP_TO_DATE, + UP_TO_DATE { + @Override + public boolean isSuccessful() { + return true; + } + }, /** * Fast-forward, HEAD points to the new commit */ - FAST_FORWARD; + FAST_FORWARD { + @Override + public boolean isSuccessful() { + return true; + } + }; + + /** + * @return whether the status indicates a successful result + */ + public abstract boolean isSuccessful(); } static final RebaseResult OK_RESULT = new RebaseResult(Status.OK); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java index 79f2297b26..8559483292 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -60,6 +60,7 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; @@ -386,64 +387,68 @@ public class DirCacheCheckout { MissingObjectException, IncorrectObjectTypeException, CheckoutConflictException, IndexWriteException { toBeDeleted.clear(); - if (headCommitTree != null) - preScanTwoTrees(); - else - prescanOneTree(); - if (!conflicts.isEmpty()) { - if (failOnConflict) { - dc.unlock(); - throw new CheckoutConflictException(conflicts.toArray(new String[conflicts.size()])); - } else - cleanUpConflicts(); - } + ObjectReader objectReader = repo.getObjectDatabase().newReader(); + try { + if (headCommitTree != null) + preScanTwoTrees(); + else + prescanOneTree(); - // update our index - builder.finish(); - - File file=null; - String last = ""; - // when deleting files process them in the opposite order as they have - // been reported. This ensures the files are deleted before we delete - // their parent folders - for (int i = removed.size() - 1; i >= 0; i--) { - String r = removed.get(i); - file = new File(repo.getWorkTree(), r); - if (!file.delete() && file.exists()) - toBeDeleted.add(r); - else { - if (!isSamePrefix(r, last)) - removeEmptyParents(new File(repo.getWorkTree(), last)); - last = r; + if (!conflicts.isEmpty()) { + if (failOnConflict) { + dc.unlock(); + throw new CheckoutConflictException(conflicts.toArray(new String[conflicts.size()])); + } else + cleanUpConflicts(); } - } - if (file != null) - removeEmptyParents(file); - for (String path : updated.keySet()) { - // ... create/overwrite this file ... - file = new File(repo.getWorkTree(), path); - if (!file.getParentFile().mkdirs()) { - // ignore + // update our index + builder.finish(); + + File file = null; + String last = ""; + // when deleting files process them in the opposite order as they have + // been reported. This ensures the files are deleted before we delete + // their parent folders + for (int i = removed.size() - 1; i >= 0; i--) { + String r = removed.get(i); + file = new File(repo.getWorkTree(), r); + if (!file.delete() && file.exists()) + toBeDeleted.add(r); + else { + if (!isSamePrefix(r, last)) + removeEmptyParents(new File(repo.getWorkTree(), last)); + last = r; + } } + if (file != null) + removeEmptyParents(file); + + for (String path : updated.keySet()) { + // ... create/overwrite this file ... + file = new File(repo.getWorkTree(), path); + if (!file.getParentFile().mkdirs()) { + // ignore + } - DirCacheEntry entry = dc.getEntry(path); - - // submodules are handled with separate operations - if (FileMode.GITLINK.equals(entry.getRawMode())) - continue; + DirCacheEntry entry = dc.getEntry(path); - checkoutEntry(repo, file, entry); - } + // submodules are handled with separate operations + if (FileMode.GITLINK.equals(entry.getRawMode())) + continue; + checkoutEntry(repo, file, entry, objectReader); + } - // commit the index builder - a new index is persisted - if (!builder.commit()) { - dc.unlock(); - throw new IndexWriteException(); + // commit the index builder - a new index is persisted + if (!builder.commit()) { + dc.unlock(); + throw new IndexWriteException(); + } + } finally { + objectReader.release(); } - return toBeDeleted.size() == 0; } @@ -848,22 +853,61 @@ public class DirCacheCheckout { * Updates the file in the working tree with content and mode from an entry * in the index. The new content is first written to a new temporary file in * the same directory as the real file. Then that new file is renamed to the + * final filename. Use this method only for checkout of a single entry. + * Otherwise use + * {@code checkoutEntry(Repository, File f, DirCacheEntry, ObjectReader)} + * instead which allows to reuse one {@code ObjectReader} for multiple + * entries. + * + * <p> + * TODO: this method works directly on File IO, we may need another + * abstraction (like WorkingTreeIterator). This way we could tell e.g. + * Eclipse that Files in the workspace got changed + * </p> + * + * @param repository + * @param f + * the file to be modified. The parent directory for this file + * has to exist already + * @param entry + * the entry containing new mode and content + * @throws IOException + */ + public static void checkoutEntry(final Repository repository, File f, + DirCacheEntry entry) throws IOException { + ObjectReader or = repository.newObjectReader(); + try { + checkoutEntry(repository, f, entry, repository.newObjectReader()); + } finally { + or.release(); + } + } + + /** + * Updates the file in the working tree with content and mode from an entry + * in the index. The new content is first written to a new temporary file in + * the same directory as the real file. Then that new file is renamed to the * final filename. * + * <p> * TODO: this method works directly on File IO, we may need another * abstraction (like WorkingTreeIterator). This way we could tell e.g. * Eclipse that Files in the workspace got changed + * </p> + * * @param repo * @param f * the file to be modified. The parent directory for this file * has to exist already * @param entry * the entry containing new mode and content + * @param or + * object reader to use for checkout * @throws IOException */ public static void checkoutEntry(final Repository repo, File f, - DirCacheEntry entry) throws IOException { - ObjectLoader ol = repo.open(entry.getObjectId()); + DirCacheEntry entry, ObjectReader or) throws IOException { + ObjectLoader ol = or.open(entry.getObjectId()); File parentDir = f.getParentFile(); File tmpFile = File.createTempFile("._" + f.getName(), null, parentDir); FileOutputStream channel = new FileOutputStream(tmpFile); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java index 59354ddb70..3db720a927 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java @@ -60,6 +60,16 @@ public class PackInvalidException extends IOException { * path of the invalid pack file. */ public PackInvalidException(final File path) { - super(MessageFormat.format(JGitText.get().packFileInvalid, path.getAbsolutePath())); + this(path.getAbsolutePath()); + } + + /** + * Construct a pack invalid error. + * + * @param path + * path of the invalid pack file. + */ + public PackInvalidException(final String path) { + super(MessageFormat.format(JGitText.get().packFileInvalid, path)); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index aab8a8e619..b51a954ca9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -1137,7 +1137,7 @@ public abstract class Repository { fos.close(); } } else { - FileUtils.delete(mergeMsgFile); + FileUtils.delete(mergeMsgFile, FileUtils.SKIP_MISSING); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java index d347b59a0b..b763568951 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -238,19 +238,23 @@ public class ResolveMerger extends ThreeWayMerger { } private void checkout() throws NoWorkTreeException, IOException { - for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut.entrySet()) { - File f = new File(db.getWorkTree(), entry.getKey()); - if (entry.getValue() != null) { - createDir(f.getParentFile()); - DirCacheCheckout.checkoutEntry(db, - f, - entry.getValue()); - } else { - if (!f.delete()) - failingPaths.put(entry.getKey(), - MergeFailureReason.COULD_NOT_DELETE); + ObjectReader r = db.getObjectDatabase().newReader(); + try { + for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut + .entrySet()) { + File f = new File(db.getWorkTree(), entry.getKey()); + if (entry.getValue() != null) { + createDir(f.getParentFile()); + DirCacheCheckout.checkoutEntry(db, f, entry.getValue(), r); + } else { + if (!f.delete()) + failingPaths.put(entry.getKey(), + MergeFailureReason.COULD_NOT_DELETE); + } + modifiedFiles.add(entry.getKey()); } - modifiedFiles.add(entry.getKey()); + } finally { + r.release(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java index 5d93126a96..9aeb44d58f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java @@ -43,6 +43,10 @@ package org.eclipse.jgit.revwalk; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; + import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; @@ -51,13 +55,12 @@ import java.util.List; import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.util.RawParseUtils; /** * Specialized subclass of RevWalk to include trees, blobs and tags. @@ -76,6 +79,13 @@ import org.eclipse.jgit.treewalk.CanonicalTreeParser; * commits that are returned first. */ public class ObjectWalk extends RevWalk { + private static final int ID_SZ = 20; + private static final int TYPE_SHIFT = 12; + private static final int TYPE_TREE = 0040000 >>> TYPE_SHIFT; + private static final int TYPE_SYMLINK = 0120000 >>> TYPE_SHIFT; + private static final int TYPE_FILE = 0100000 >>> TYPE_SHIFT; + private static final int TYPE_GITLINK = 0160000 >>> TYPE_SHIFT; + /** * Indicates a non-RevCommit is in {@link #pendingObjects}. * <p> @@ -85,22 +95,24 @@ public class ObjectWalk extends RevWalk { */ private static final int IN_PENDING = RevWalk.REWRITE; - private static final byte[] EMPTY_PATH = {}; - - private CanonicalTreeParser treeWalk; - private List<RevObject> rootObjects; private BlockObjQueue pendingObjects; - private RevTree currentTree; - - private RevObject last; - private RevCommit firstCommit; private RevCommit lastCommit; + private TreeVisit freeVisit; + + private TreeVisit currVisit; + + private byte[] pathBuf; + + private int pathLen; + + private boolean boundary; + /** * Create a new revision and object walker for a given repository. * @@ -123,7 +135,7 @@ public class ObjectWalk extends RevWalk { super(or); rootObjects = new ArrayList<RevObject>(); pendingObjects = new BlockObjQueue(); - treeWalk = new CanonicalTreeParser(); + pathBuf = new byte[256]; } /** @@ -213,7 +225,7 @@ public class ObjectWalk extends RevWalk { IncorrectObjectTypeException, IOException { while (o instanceof RevTag) { o.flags |= UNINTERESTING; - if (hasRevSort(RevSort.BOUNDARY)) + if (boundary) addObject(o); o = ((RevTag) o).getObject(); parseHeaders(o); @@ -226,9 +238,20 @@ public class ObjectWalk extends RevWalk { else o.flags |= UNINTERESTING; - if (o.getType() != Constants.OBJ_COMMIT && hasRevSort(RevSort.BOUNDARY)) { + if (o.getType() != OBJ_COMMIT && boundary) addObject(o); - } + } + + @Override + public void sort(RevSort s) { + super.sort(s); + boundary = hasRevSort(RevSort.BOUNDARY); + } + + @Override + public void sort(RevSort s, boolean use) { + super.sort(s, use); + boundary = hasRevSort(RevSort.BOUNDARY); } @Override @@ -236,11 +259,14 @@ public class ObjectWalk extends RevWalk { IncorrectObjectTypeException, IOException { for (;;) { final RevCommit r = super.next(); - if (r == null) + if (r == null) { + if (firstCommit != null) + reader.walkAdviceBeginTrees(this, firstCommit, lastCommit); return null; + } if ((r.flags & UNINTERESTING) != 0) { markTreeUninteresting(r.getTree()); - if (hasRevSort(RevSort.BOUNDARY)) + if (boundary) return r; continue; } @@ -268,85 +294,180 @@ public class ObjectWalk extends RevWalk { */ public RevObject nextObject() throws MissingObjectException, IncorrectObjectTypeException, IOException { - if (last != null) - treeWalk = last instanceof RevTree ? enter(last) : treeWalk.next(); - - while (!treeWalk.eof()) { - final FileMode mode = treeWalk.getEntryFileMode(); - switch (mode.getObjectType()) { - case Constants.OBJ_BLOB: { - treeWalk.getEntryObjectId(idBuffer); - final RevBlob o = lookupBlob(idBuffer); - if ((o.flags & SEEN) != 0) - break; - o.flags |= SEEN; - if (shouldSkipObject(o)) - break; - last = o; - return o; - } - case Constants.OBJ_TREE: { - treeWalk.getEntryObjectId(idBuffer); - final RevTree o = lookupTree(idBuffer); - if ((o.flags & SEEN) != 0) - break; - o.flags |= SEEN; - if (shouldSkipObject(o)) - break; - last = o; - return o; - } - default: - if (FileMode.GITLINK.equals(mode)) - break; - treeWalk.getEntryObjectId(idBuffer); - throw new CorruptObjectException(MessageFormat.format(JGitText.get().corruptObjectInvalidMode3 - , mode , idBuffer.name() , treeWalk.getEntryPathString() , currentTree.name())); - } + pathLen = 0; + + TreeVisit tv = currVisit; + while (tv != null) { + byte[] buf = tv.buf; + for (int ptr = tv.ptr; ptr < buf.length;) { + int startPtr = ptr; + ptr = findObjectId(buf, ptr); + idBuffer.fromRaw(buf, ptr); + ptr += ID_SZ; + + RevObject obj = objects.get(idBuffer); + if (obj != null && (obj.flags & SEEN) != 0) + continue; - treeWalk = treeWalk.next(); - } + int mode = parseMode(buf, startPtr, ptr, tv); + int flags; + switch (mode >>> TYPE_SHIFT) { + case TYPE_FILE: + case TYPE_SYMLINK: + if (obj == null) { + obj = new RevBlob(idBuffer); + obj.flags = SEEN; + objects.add(obj); + return obj; + } + if (!(obj instanceof RevBlob)) + throw new IncorrectObjectTypeException(obj, OBJ_BLOB); + obj.flags = flags = obj.flags | SEEN; + if ((flags & UNINTERESTING) == 0) + return obj; + if (boundary) + return obj; + continue; - if (firstCommit != null) { - reader.walkAdviceBeginTrees(this, firstCommit, lastCommit); - firstCommit = null; - lastCommit = null; + case TYPE_TREE: + if (obj == null) { + obj = new RevTree(idBuffer); + obj.flags = SEEN; + objects.add(obj); + return enterTree(obj); + } + if (!(obj instanceof RevTree)) + throw new IncorrectObjectTypeException(obj, OBJ_TREE); + obj.flags = flags = obj.flags | SEEN; + if ((flags & UNINTERESTING) == 0) + return enterTree(obj); + if (boundary) + return enterTree(obj); + continue; + + case TYPE_GITLINK: + continue; + + default: + throw new CorruptObjectException(MessageFormat.format( + JGitText.get().corruptObjectInvalidMode3, + String.format("%o", mode), idBuffer.name(), + RawParseUtils.decode(buf, tv.namePtr, tv.nameEnd), + tv.obj)); + } + } + + currVisit = tv.parent; + releaseTreeVisit(tv); + tv = currVisit; } - last = null; for (;;) { - final RevObject o = pendingObjects.next(); + RevObject o = pendingObjects.next(); if (o == null) { reader.walkAdviceEnd(); return null; } - if ((o.flags & SEEN) != 0) - continue; - o.flags |= SEEN; - if (shouldSkipObject(o)) + int flags = o.flags; + if ((flags & SEEN) != 0) continue; - if (o instanceof RevTree) { - currentTree = (RevTree) o; - treeWalk = treeWalk.resetRoot(reader, currentTree); + flags |= SEEN; + o.flags = flags; + if ((flags & UNINTERESTING) == 0 | boundary) { + if (o instanceof RevTree) { + tv = newTreeVisit(o); + tv.parent = null; + currVisit = tv; + } + return o; } - return o; } } - private CanonicalTreeParser enter(RevObject tree) throws IOException { - CanonicalTreeParser p = treeWalk.createSubtreeIterator0(reader, tree); - if (p.eof()) { - // We can't tolerate the subtree being an empty tree, as - // that will break us out early before we visit all names. - // If it is, advance to the parent's next record. - // - return treeWalk.next(); + private RevObject enterTree(RevObject obj) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + TreeVisit tv = newTreeVisit(obj); + tv.parent = currVisit; + currVisit = tv; + return obj; + } + + private static int findObjectId(byte[] buf, int ptr) { + // Skip over the mode and name until the NUL before the ObjectId + // can be located. Skip the NUL as the function returns. + for (;;) { + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; + + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; + + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; + + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; + if (buf[++ptr] == 0) return ++ptr; } - return p; } - private final boolean shouldSkipObject(final RevObject o) { - return (o.flags & UNINTERESTING) != 0 && !hasRevSort(RevSort.BOUNDARY); + private static int parseMode(byte[] buf, int startPtr, int recEndPtr, TreeVisit tv) { + int mode = buf[startPtr] - '0'; + for (;;) { + byte c = buf[++startPtr]; + if (' ' == c) + break; + mode <<= 3; + mode += c - '0'; + + c = buf[++startPtr]; + if (' ' == c) + break; + mode <<= 3; + mode += c - '0'; + + c = buf[++startPtr]; + if (' ' == c) + break; + mode <<= 3; + mode += c - '0'; + + c = buf[++startPtr]; + if (' ' == c) + break; + mode <<= 3; + mode += c - '0'; + + c = buf[++startPtr]; + if (' ' == c) + break; + mode <<= 3; + mode += c - '0'; + + c = buf[++startPtr]; + if (' ' == c) + break; + mode <<= 3; + mode += c - '0'; + + c = buf[++startPtr]; + if (' ' == c) + break; + mode <<= 3; + mode += c - '0'; + } + + tv.ptr = recEndPtr; + tv.namePtr = startPtr + 1; + tv.nameEnd = recEndPtr - (ID_SZ + 1); + return mode; } /** @@ -383,7 +504,7 @@ public class ObjectWalk extends RevWalk { if (o == null) break; if (o instanceof RevBlob && !reader.has(o)) - throw new MissingObjectException(o, Constants.TYPE_BLOB); + throw new MissingObjectException(o, OBJ_BLOB); } } @@ -401,7 +522,12 @@ public class ObjectWalk extends RevWalk { * has no path, such as for annotated tags or root level trees. */ public String getPathString() { - return last != null ? treeWalk.getEntryPathString() : null; + if (pathLen == 0) { + pathLen = updatePathBuf(currVisit); + if (pathLen == 0) + return null; + } + return RawParseUtils.decode(pathBuf, 0, pathLen); } /** @@ -413,28 +539,104 @@ public class ObjectWalk extends RevWalk { * @return path hash code; any integer may be returned. */ public int getPathHashCode() { - return last != null ? treeWalk.getEntryPathHashCode() : 0; + TreeVisit tv = currVisit; + if (tv == null) + return 0; + + int nameEnd = tv.nameEnd; + if (nameEnd == 0) { + // When nameEnd == 0 the subtree is itself the current path + // being visited. The name hash must be obtained from its + // parent tree. If there is no parent, this is a root tree with + // a hash code of 0. + tv = tv.parent; + if (tv == null) + return 0; + nameEnd = tv.nameEnd; + } + + byte[] buf; + int ptr; + + if (16 <= (nameEnd - tv.namePtr)) { + buf = tv.buf; + ptr = nameEnd - 16; + } else { + nameEnd = pathLen; + if (nameEnd == 0) { + nameEnd = updatePathBuf(currVisit); + pathLen = nameEnd; + } + buf = pathBuf; + ptr = Math.max(0, nameEnd - 16); + } + + int hash = 0; + for (; ptr < nameEnd; ptr++) { + byte c = buf[ptr]; + if (c != ' ') + hash = (hash >>> 2) + (c << 24); + } + return hash; } /** @return the internal buffer holding the current path. */ public byte[] getPathBuffer() { - return last != null ? treeWalk.getEntryPathBuffer() : EMPTY_PATH; + if (pathLen == 0) + pathLen = updatePathBuf(currVisit); + return pathBuf; } /** @return length of the path in {@link #getPathBuffer()}. */ public int getPathLength() { - return last != null ? treeWalk.getEntryPathLength() : 0; + if (pathLen == 0) + pathLen = updatePathBuf(currVisit); + return pathLen; + } + + private int updatePathBuf(TreeVisit tv) { + if (tv == null) + return 0; + + // If nameEnd == 0 this tree has not yet contributed an entry. + // Update only for the parent, which if null will be empty. + int nameEnd = tv.nameEnd; + if (nameEnd == 0) + return updatePathBuf(tv.parent); + + int ptr = tv.pathLen; + if (ptr == 0) { + ptr = updatePathBuf(tv.parent); + if (ptr == pathBuf.length) + growPathBuf(ptr); + if (ptr != 0) + pathBuf[ptr++] = '/'; + tv.pathLen = ptr; + } + + int namePtr = tv.namePtr; + int nameLen = nameEnd - namePtr; + int end = ptr + nameLen; + while (pathBuf.length < end) + growPathBuf(ptr); + System.arraycopy(tv.buf, namePtr, pathBuf, ptr, nameLen); + return end; + } + + private void growPathBuf(int ptr) { + byte[] newBuf = new byte[pathBuf.length << 1]; + System.arraycopy(pathBuf, 0, newBuf, 0, ptr); + pathBuf = newBuf; } @Override public void dispose() { super.dispose(); pendingObjects = new BlockObjQueue(); - treeWalk = new CanonicalTreeParser(); - currentTree = null; - last = null; firstCommit = null; lastCommit = null; + currVisit = null; + freeVisit = null; } @Override @@ -446,11 +648,10 @@ public class ObjectWalk extends RevWalk { rootObjects = new ArrayList<RevObject>(); pendingObjects = new BlockObjQueue(); - treeWalk = new CanonicalTreeParser(); - currentTree = null; - last = null; firstCommit = null; lastCommit = null; + currVisit = null; + freeVisit = null; } private void addObject(final RevObject o) { @@ -468,36 +669,90 @@ public class ObjectWalk extends RevWalk { return; tree.flags |= UNINTERESTING; - treeWalk = treeWalk.resetRoot(reader, tree); - while (!treeWalk.eof()) { - final FileMode mode = treeWalk.getEntryFileMode(); - final int sType = mode.getObjectType(); + byte[] raw = reader.open(tree, OBJ_TREE).getCachedBytes(); + for (int ptr = 0; ptr < raw.length;) { + byte c = raw[ptr]; + int mode = c - '0'; + for (;;) { + c = raw[++ptr]; + if (' ' == c) + break; + mode <<= 3; + mode += c - '0'; + } + while (raw[++ptr] != 0) { + // Skip entry name. + } + ptr++; // Skip NUL after entry name. - switch (sType) { - case Constants.OBJ_BLOB: { - treeWalk.getEntryObjectId(idBuffer); + switch (mode >>> TYPE_SHIFT) { + case TYPE_FILE: + case TYPE_SYMLINK: + idBuffer.fromRaw(raw, ptr); lookupBlob(idBuffer).flags |= UNINTERESTING; break; - } - case Constants.OBJ_TREE: { - treeWalk.getEntryObjectId(idBuffer); - final RevTree t = lookupTree(idBuffer); - if ((t.flags & UNINTERESTING) == 0) { - t.flags |= UNINTERESTING; - treeWalk = treeWalk.createSubtreeIterator0(reader, t); - continue; - } + + case TYPE_TREE: + idBuffer.fromRaw(raw, ptr); + markTreeUninteresting(lookupTree(idBuffer)); break; - } + + case TYPE_GITLINK: + break; + default: - if (FileMode.GITLINK.equals(mode)) - break; - treeWalk.getEntryObjectId(idBuffer); - throw new CorruptObjectException(MessageFormat.format(JGitText.get().corruptObjectInvalidMode3 - , mode , idBuffer.name() , treeWalk.getEntryPathString() , tree)); + idBuffer.fromRaw(raw, ptr); + throw new CorruptObjectException(MessageFormat.format(JGitText + .get().corruptObjectInvalidMode3, String.format("%o", + mode), idBuffer.name(), "", tree)); } + ptr += ID_SZ; + } + } - treeWalk = treeWalk.next(); + private TreeVisit newTreeVisit(RevObject obj) throws LargeObjectException, + MissingObjectException, IncorrectObjectTypeException, IOException { + TreeVisit tv = freeVisit; + if (tv != null) { + freeVisit = tv.parent; + tv.ptr = 0; + tv.namePtr = 0; + tv.nameEnd = 0; + tv.pathLen = 0; + } else { + tv = new TreeVisit(); } + tv.obj = obj; + tv.buf = reader.open(obj, OBJ_TREE).getCachedBytes(); + return tv; + } + + private void releaseTreeVisit(TreeVisit tv) { + tv.buf = null; + tv.parent = freeVisit; + freeVisit = tv; + } + + private static class TreeVisit { + /** Parent tree visit that entered this tree, null if root tree. */ + TreeVisit parent; + + /** The RevTree currently being iterated through. */ + RevObject obj; + + /** Canonical encoding of the tree named by {@link #obj}. */ + byte[] buf; + + /** Index of next entry to parse in {@link #buf}. */ + int ptr; + + /** Start of the current name entry in {@link #buf}. */ + int namePtr; + + /** One past end of name, {@code nameEnd - namePtr} is the length. */ + int nameEnd; + + /** Number of bytes in the path leading up to this tree. */ + int pathLen; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java index a617dd1dc5..cbf075e72a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -170,7 +170,7 @@ public class RevWalk implements Iterable<RevCommit> { final MutableObjectId idBuffer; - private ObjectIdOwnerMap<RevObject> objects; + ObjectIdOwnerMap<RevObject> objects; private int freeFlags = APP_FLAGS; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java index fc1b748f5e..9b43b39ea9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndex.java @@ -48,11 +48,13 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.text.MessageFormat; import java.util.Iterator; import java.util.Set; import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; @@ -91,18 +93,7 @@ public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> { public static PackIndex open(final File idxFile) throws IOException { final FileInputStream fd = new FileInputStream(idxFile); try { - final byte[] hdr = new byte[8]; - IO.readFully(fd, hdr, 0, hdr.length); - if (isTOC(hdr)) { - final int v = NB.decodeInt32(hdr, 4); - switch (v) { - case 2: - return new PackIndexV2(fd); - default: - throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackIndexVersion, v)); - } - } - return new PackIndexV1(fd, hdr); + return read(fd); } catch (IOException ioe) { final String path = idxFile.getAbsolutePath(); final IOException err; @@ -118,6 +109,39 @@ public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> { } } + /** + * Read an existing pack index file from a buffered stream. + * <p> + * The format of the file will be automatically detected and a proper access + * implementation for that format will be constructed and returned to the + * caller. The file may or may not be held open by the returned instance. + * + * @param fd + * stream to read the index file from. The stream must be + * buffered as some small IOs are performed against the stream. + * The caller is responsible for closing the stream. + * @return a copy of the index in-memory. + * @throws IOException + * the stream cannot be read. + * @throws CorruptObjectException + * the stream does not contain a valid pack index. + */ + public static PackIndex read(InputStream fd) throws IOException, + CorruptObjectException { + final byte[] hdr = new byte[8]; + IO.readFully(fd, hdr, 0, hdr.length); + if (isTOC(hdr)) { + final int v = NB.decodeInt32(hdr, 4); + switch (v) { + case 2: + return new PackIndexV2(fd); + default: + throw new IOException(MessageFormat.format(JGitText.get().unsupportedPackIndexVersion, v)); + } + } + return new PackIndexV1(fd, hdr); + } + private static boolean isTOC(final byte[] h) { final byte[] toc = PackIndexWriter.TOC; for (int i = 0; i < toc.length; i++) @@ -159,7 +183,7 @@ public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> { * @return number of objects in this index, and likewise in the associated * pack that this index was generated from. */ - abstract long getObjectCount(); + public abstract long getObjectCount(); /** * Obtain the total number of objects needing 64 bit offsets. @@ -167,7 +191,7 @@ public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> { * @return number of objects in this index using a 64 bit offset; that is an * object positioned after the 2 GB position within the file. */ - abstract long getOffset64Count(); + public abstract long getOffset64Count(); /** * Get ObjectId for the n-th object entry returned by {@link #iterator()}. @@ -188,7 +212,7 @@ public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> { * {@link MutableEntry} is 0, the second is 1, etc. * @return the ObjectId for the corresponding entry. */ - abstract ObjectId getObjectId(long nthPosition); + public abstract ObjectId getObjectId(long nthPosition); /** * Get ObjectId for the n-th object entry returned by {@link #iterator()}. @@ -210,7 +234,7 @@ public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> { * etc. Positions past 2**31-1 are negative, but still valid. * @return the ObjectId for the corresponding entry. */ - final ObjectId getObjectId(final int nthPosition) { + public final ObjectId getObjectId(final int nthPosition) { if (nthPosition >= 0) return getObjectId((long) nthPosition); final int u31 = nthPosition >>> 1; @@ -227,7 +251,7 @@ public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> { * object does not exist in this index and is thus not stored in the * associated pack. */ - abstract long findOffset(AnyObjectId objId); + public abstract long findOffset(AnyObjectId objId); /** * Retrieve stored CRC32 checksum of the requested object raw-data @@ -241,17 +265,31 @@ public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> { * @throws UnsupportedOperationException * when this index doesn't support CRC32 checksum */ - abstract long findCRC32(AnyObjectId objId) throws MissingObjectException, - UnsupportedOperationException; + public abstract long findCRC32(AnyObjectId objId) + throws MissingObjectException, UnsupportedOperationException; /** * Check whether this index supports (has) CRC32 checksums for objects. * * @return true if CRC32 is stored, false otherwise */ - abstract boolean hasCRC32Support(); + public abstract boolean hasCRC32Support(); - abstract void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, + /** + * Find objects matching the prefix abbreviation. + * + * @param matches + * set to add any located ObjectIds to. This is an output + * parameter. + * @param id + * prefix to search for. + * @param matchLimit + * maximum number of results to return. At most this many + * ObjectIds should be added to matches before returning. + * @throws IOException + * the index cannot be read. + */ + public abstract void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, int matchLimit) throws IOException; /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java index 1c682f17db..4071fb84d1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV1.java @@ -98,12 +98,13 @@ class PackIndexV1 extends PackIndex { IO.readFully(fd, packChecksum, 0, packChecksum.length); } - long getObjectCount() { + @Override + public long getObjectCount() { return objectCnt; } @Override - long getOffset64Count() { + public long getOffset64Count() { long n64 = 0; for (final MutableEntry e : this) { if (e.getOffset() >= Integer.MAX_VALUE) @@ -113,7 +114,7 @@ class PackIndexV1 extends PackIndex { } @Override - ObjectId getObjectId(final long nthPosition) { + public ObjectId getObjectId(final long nthPosition) { int levelOne = Arrays.binarySearch(idxHeader, nthPosition + 1); long base; if (levelOne >= 0) { @@ -135,7 +136,8 @@ class PackIndexV1 extends PackIndex { return ObjectId.fromRaw(idxdata[levelOne], dataIdx); } - long findOffset(final AnyObjectId objId) { + @Override + public long findOffset(final AnyObjectId objId) { final int levelOne = objId.getFirstByte(); byte[] data = idxdata[levelOne]; if (data == null) @@ -161,22 +163,23 @@ class PackIndexV1 extends PackIndex { } @Override - long findCRC32(AnyObjectId objId) { + public long findCRC32(AnyObjectId objId) { throw new UnsupportedOperationException(); } @Override - boolean hasCRC32Support() { + public boolean hasCRC32Support() { return false; } + @Override public Iterator<MutableEntry> iterator() { return new IndexV1Iterator(); } @Override - void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, int matchLimit) - throws IOException { + public void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, + int matchLimit) throws IOException { byte[] data = idxdata[id.getFirstByte()]; if (data == null) return; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java index abc8767666..44a4044ac3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexV2.java @@ -161,17 +161,17 @@ class PackIndexV2 extends PackIndex { } @Override - long getObjectCount() { + public long getObjectCount() { return objectCnt; } @Override - long getOffset64Count() { + public long getOffset64Count() { return offset64.length / 8; } @Override - ObjectId getObjectId(final long nthPosition) { + public ObjectId getObjectId(final long nthPosition) { int levelOne = Arrays.binarySearch(fanoutTable, nthPosition + 1); long base; if (levelOne >= 0) { @@ -194,7 +194,7 @@ class PackIndexV2 extends PackIndex { } @Override - long findOffset(final AnyObjectId objId) { + public long findOffset(final AnyObjectId objId) { final int levelOne = objId.getFirstByte(); final int levelTwo = binarySearchLevelTwo(objId, levelOne); if (levelTwo == -1) @@ -206,7 +206,7 @@ class PackIndexV2 extends PackIndex { } @Override - long findCRC32(AnyObjectId objId) throws MissingObjectException { + public long findCRC32(AnyObjectId objId) throws MissingObjectException { final int levelOne = objId.getFirstByte(); final int levelTwo = binarySearchLevelTwo(objId, levelOne); if (levelTwo == -1) @@ -215,17 +215,18 @@ class PackIndexV2 extends PackIndex { } @Override - boolean hasCRC32Support() { + public boolean hasCRC32Support() { return true; } + @Override public Iterator<MutableEntry> iterator() { return new EntriesIteratorV2(); } @Override - void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, int matchLimit) - throws IOException { + public void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, + int matchLimit) throws IOException { int[] data = names[id.getFirstByte()]; int max = offset32[id.getFirstByte()].length >>> 2; int high = max; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java index 21ebd1ca9c..7671cd6f1c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackIndexWriterV2.java @@ -56,6 +56,9 @@ import org.eclipse.jgit.util.NB; * @see PackIndexV2 */ class PackIndexWriterV2 extends PackIndexWriter { + private static final int MAX_OFFSET_32 = 0x7fffffff; + private static final int IS_OFFSET_64 = 0x80000000; + PackIndexWriterV2(final OutputStream dst) { super(dst); } @@ -87,10 +90,10 @@ class PackIndexWriterV2 extends PackIndexWriter { int o64 = 0; for (final PackedObjectInfo oe : entries) { final long o = oe.getOffset(); - if (o < Integer.MAX_VALUE) + if (o <= MAX_OFFSET_32) NB.encodeInt32(tmp, 0, (int) o); else - NB.encodeInt32(tmp, 0, (1 << 31) | o64++); + NB.encodeInt32(tmp, 0, IS_OFFSET_64 | o64++); out.write(tmp, 0, 4); } } @@ -98,7 +101,7 @@ class PackIndexWriterV2 extends PackIndexWriter { private void writeOffset64() throws IOException { for (final PackedObjectInfo oe : entries) { final long o = oe.getOffset(); - if (o > Integer.MAX_VALUE) { + if (MAX_OFFSET_32 < o) { NB.encodeInt64(tmp, 0, o); out.write(tmp, 0, 8); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java index 96abaeefd3..ccd621c935 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/PackReverseIndex.java @@ -61,7 +61,7 @@ import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; * @see PackIndex * @see PackFile */ -class PackReverseIndex { +public class PackReverseIndex { /** Index we were created from, and that has our ObjectId data. */ private final PackIndex index; @@ -88,7 +88,7 @@ class PackReverseIndex { * @param packIndex * forward index - entries to (reverse) index. */ - PackReverseIndex(final PackIndex packIndex) { + public PackReverseIndex(final PackIndex packIndex) { index = packIndex; final long cnt = index.getObjectCount(); @@ -135,7 +135,7 @@ class PackReverseIndex { * start offset of object to find. * @return object id for this offset, or null if no object was found. */ - ObjectId findObject(final long offset) { + public ObjectId findObject(final long offset) { if (offset <= Integer.MAX_VALUE) { final int i32 = Arrays.binarySearch(offsets32, (int) offset); if (i32 < 0) @@ -164,7 +164,7 @@ class PackReverseIndex { * @throws CorruptObjectException * when there is no object with the provided offset. */ - long findNextOffset(final long offset, final long maxOffset) + public long findNextOffset(final long offset, final long maxOffset) throws CorruptObjectException { if (offset <= Integer.MAX_VALUE) { final int i32 = Arrays.binarySearch(offsets32, (int) offset); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java index 0590f905ec..3917ff4716 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java @@ -385,10 +385,17 @@ public class RefDirectory extends RefDatabase { if (entries == null) // not a directory or an I/O error return false; if (0 < entries.length) { + for (int i = 0; i < entries.length; ++i) { + String e = entries[i]; + File f = new File(dir, e); + if (f.isDirectory()) + entries[i] += '/'; + } Arrays.sort(entries); for (String name : entries) { - File e = new File(dir, name); - if (!scanTree(prefix + name + '/', e)) + if (name.charAt(name.length() - 1) == '/') + scanTree(prefix + name, new File(dir, name)); + else scanOne(prefix + name); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java index 7206bb8b0d..91275dc098 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java @@ -190,6 +190,29 @@ public class PackConfig { } /** + * Copy an existing configuration to a new instance. + * + * @param cfg + * the source configuration to copy from. + */ + public PackConfig(PackConfig cfg) { + this.compressionLevel = cfg.compressionLevel; + this.reuseDeltas = cfg.reuseDeltas; + this.reuseObjects = cfg.reuseObjects; + this.deltaBaseAsOffset = cfg.deltaBaseAsOffset; + this.deltaCompress = cfg.deltaCompress; + this.maxDeltaDepth = cfg.maxDeltaDepth; + this.deltaSearchWindowSize = cfg.deltaSearchWindowSize; + this.deltaSearchMemoryLimit = cfg.deltaSearchMemoryLimit; + this.deltaCacheSize = cfg.deltaCacheSize; + this.deltaCacheLimit = cfg.deltaCacheLimit; + this.bigFileThreshold = cfg.bigFileThreshold; + this.threads = cfg.threads; + this.executor = cfg.executor; + this.indexVersion = cfg.indexVersion; + } + + /** * Check whether to reuse deltas existing in repository. * * Default setting: {@value #DEFAULT_REUSE_DELTAS} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java index 8a912aeb6b..8c3d52afcd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -80,6 +80,7 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AsyncObjectSizeQueue; +import org.eclipse.jgit.lib.BatchingProgressMonitor; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; @@ -640,10 +641,21 @@ public class PackWriter { if (writeMonitor == null) writeMonitor = NullProgressMonitor.INSTANCE; - if (reuseSupport != null && ( + boolean needSearchForReuse = reuseSupport != null && ( reuseDeltas || config.isReuseObjects() - || !cachedPacks.isEmpty())) + || !cachedPacks.isEmpty()); + + if (compressMonitor instanceof BatchingProgressMonitor) { + long delay = 1000; + if (needSearchForReuse && config.isDeltaCompress()) + delay = 500; + ((BatchingProgressMonitor) compressMonitor).setDelayStart( + delay, + TimeUnit.MILLISECONDS); + } + + if (needSearchForReuse) searchForReuse(compressMonitor); if (config.isDeltaCompress()) searchForDeltas(compressMonitor); @@ -897,10 +909,10 @@ public class PackWriter { private int findObjectsNeedingDelta(ObjectToPack[] list, int cnt, int type) { for (ObjectToPack otp : objectsLists[type]) { - if (otp.isReuseAsIs()) // already reusing a representation - continue; if (otp.isDoNotDelta()) // delta is disabled for this path continue; + if (otp.isDeltaRepresentation()) // already reusing a delta + continue; otp.setWeight(0); list[cnt++] = otp; } @@ -1425,20 +1437,29 @@ public class PackWriter { } commits = null; - BaseSearch bases = new BaseSearch(countingMonitor, baseTrees, // - objectsMap, edgeObjects, reader); - RevObject o; - while ((o = walker.nextObject()) != null) { - if (o.has(RevFlag.UNINTERESTING)) - continue; - - int pathHash = walker.getPathHashCode(); - byte[] pathBuf = walker.getPathBuffer(); - int pathLen = walker.getPathLength(); + if (thin && !baseTrees.isEmpty()) { + BaseSearch bases = new BaseSearch(countingMonitor, baseTrees, // + objectsMap, edgeObjects, reader); + RevObject o; + while ((o = walker.nextObject()) != null) { + if (o.has(RevFlag.UNINTERESTING)) + continue; - bases.addBase(o.getType(), pathBuf, pathLen, pathHash); - addObject(o, pathHash); - countingMonitor.update(1); + int pathHash = walker.getPathHashCode(); + byte[] pathBuf = walker.getPathBuffer(); + int pathLen = walker.getPathLength(); + bases.addBase(o.getType(), pathBuf, pathLen, pathHash); + addObject(o, pathHash); + countingMonitor.update(1); + } + } else { + RevObject o; + while ((o = walker.nextObject()) != null) { + if (o.has(RevFlag.UNINTERESTING)) + continue; + addObject(o, walker.getPathHashCode()); + countingMonitor.update(1); + } } for (CachedPack pack : cachedPacks) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java index 4bbe3a0048..1b30e859ec 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java @@ -54,6 +54,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -61,6 +62,7 @@ import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BatchingProgressMonitor; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.InflaterCache; import org.eclipse.jgit.lib.MutableObjectId; @@ -478,6 +480,12 @@ public abstract class PackParser { if (!deferredCheckBlobs.isEmpty()) doDeferredCheckBlobs(); if (deltaCount > 0) { + if (resolving instanceof BatchingProgressMonitor) { + ((BatchingProgressMonitor) resolving).setDelayStart( + 1000, + TimeUnit.MILLISECONDS); + } + resolving.beginTask(JGitText.get().resolvingDeltas, deltaCount); resolveDeltas(resolving); if (entryCount < objectCount) { if (!isAllowThin()) { @@ -494,6 +502,7 @@ public abstract class PackParser { (objectCount - entryCount))); } } + resolving.endTask(); } packDigest = null; @@ -511,7 +520,6 @@ public abstract class PackParser { inflater.release(); } finally { inflater = null; - objectDatabase.close(); } } return null; // By default there is no locking. @@ -519,20 +527,17 @@ public abstract class PackParser { private void resolveDeltas(final ProgressMonitor progress) throws IOException { - progress.beginTask(JGitText.get().resolvingDeltas, deltaCount); final int last = entryCount; for (int i = 0; i < last; i++) { - final int before = entryCount; - resolveDeltas(entries[i]); - progress.update(entryCount - before); + resolveDeltas(entries[i], progress); if (progress.isCancelled()) throw new IOException( JGitText.get().downloadCancelledDuringIndexing); } - progress.endTask(); } - private void resolveDeltas(final PackedObjectInfo oe) throws IOException { + private void resolveDeltas(final PackedObjectInfo oe, + ProgressMonitor progress) throws IOException { UnresolvedDelta children = firstChildOf(oe); if (children == null) return; @@ -560,12 +565,14 @@ public abstract class PackParser { .getOffset())); } - resolveDeltas(visit.next(), info.type, info); + resolveDeltas(visit.next(), info.type, info, progress); } private void resolveDeltas(DeltaVisit visit, final int type, - ObjectTypeAndSize info) throws IOException { + ObjectTypeAndSize info, ProgressMonitor progress) + throws IOException { do { + progress.update(1); info = openDatabase(visit.delta, info); switch (info.type) { case Constants.OBJ_OFS_DELTA: @@ -750,7 +757,8 @@ public abstract class PackParser { entries[entryCount++] = oe; visit.nextChild = firstChildOf(oe); - resolveDeltas(visit.next(), typeCode, new ObjectTypeAndSize()); + resolveDeltas(visit.next(), typeCode, + new ObjectTypeAndSize(), progress); if (progress.isCancelled()) throw new IOException( |