From a86566dcf04df51a445051cf9d12ba9451020a3e Mon Sep 17 00:00:00 2001 From: Dmitry Neverov Date: Fri, 21 Aug 2015 17:44:49 +0200 Subject: [PATCH 01/89] Fix hanging fetch via SSH Signaling the need to flush() only via the interrupted status of a copying thread doesn't work realiably with jsch. The write() method of com.jcraft.jsch.Session catches the InterruptedException in several places. As a result StreamCopyThread can easily miss the interrupt if it was interrupted during the dst.write() or dst.flush() call. When it happens, StreamCopyThread will not send some data to the remote side and will not get the response back, because remote side will wait for more data from us. The flushCount field incremented during flush() method guarantees we don't miss flush() even if jsch catches InterruptedException in dst.write() or dst.flush() calls. Checking the flushCount after dst.write() is needed because dst.write() can clear interrupt status, in this case the next blocking src.read() won't throw an exception and we will not call flush(). Flush is performed only after src.read() was blocked and thrown an InterruptedIOException exception, this guarantees that we flush all the data available in src so far (src.read() doesn't block while more is available). FlushCount is reset to 0 only when there were no flush() calls since last blocked read, that means we flushed all data available in src. If there were flush() calls, the interrupt status is restored, so next blocked read will throw InterruptedException and we will flush() again. Change-Id: I692b226edaff502f06235ec05da9052b5fe6478a Signed-off-by: Dmitry Neverov --- .../jgit/util/io/StreamCopyThread.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java index 24b8b5333..8d39a22ac 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java @@ -47,6 +47,7 @@ import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicInteger; /** Thread to copy from an input stream to an output stream. */ public class StreamCopyThread extends Thread { @@ -58,6 +59,8 @@ public class StreamCopyThread extends Thread { private volatile boolean done; + private final AtomicInteger flushCount = new AtomicInteger(0); + /** * Create a thread to copy data from an input stream to an output stream. * @@ -82,6 +85,7 @@ public StreamCopyThread(final InputStream i, final OutputStream o) { * the request. */ public void flush() { + flushCount.incrementAndGet(); interrupt(); } @@ -109,22 +113,30 @@ public void halt() throws InterruptedException { public void run() { try { final byte[] buf = new byte[BUFFER_SIZE]; - int interruptCounter = 0; + int flushCountBeforeRead = 0; + boolean readInterrupted = false; for (;;) { try { - if (interruptCounter > 0) { + if (readInterrupted) { dst.flush(); - interruptCounter--; + readInterrupted = false; + if (!flushCount.compareAndSet(flushCountBeforeRead, 0)) { + // There was a flush() call since last blocked read. + // Set interrupt status, so next blocked read will throw + // an InterruptedIOException and we will flush again. + interrupt(); + } } if (done) break; + flushCountBeforeRead = flushCount.get(); final int n; try { n = src.read(buf); } catch (InterruptedIOException wakey) { - interruptCounter++; + readInterrupted = true; continue; } if (n < 0) @@ -141,7 +153,7 @@ public void run() { // set interrupt status, which will be checked // when we block in src.read - if (writeInterrupted) + if (writeInterrupted || flushCount.get() > 0) interrupt(); break; } From d29b2b6762a96d8ecd20bfc8855b3f9dfdfcce67 Mon Sep 17 00:00:00 2001 From: Marc Strapetz Date: Wed, 21 Oct 2015 12:03:53 +0200 Subject: [PATCH 02/89] Refspec: loosen restrictions on wildcard "*" Since Git 2.6 wildcard restrictions for refspecs have been loosened: refspecs like "refs/heads/*foo:refs/heads/foo*" are valid now. See Git commit 8d3981ccbed9fc211b4e67105015179d9d2a5692 Change-Id: Icb78afbd282c425173b3a7bc10eadc4015689bb8 Signed-off-by: Marc Strapetz --- .../eclipse/jgit/transport/RefSpecTest.java | 50 +++++++++++++------ .../org/eclipse/jgit/transport/RefSpec.java | 4 -- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java index 3f5fcbbf0..4f833509d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java @@ -340,6 +340,41 @@ public void testWildcardInMiddleOfDestionation() { assertEquals("refs/heads/foo", c.getSource()); } + @Test + public void testWildcardAfterText1() { + RefSpec a = new RefSpec("refs/heads/*/for-linus:refs/remotes/mine/*-blah"); + assertTrue(a.isWildcard()); + assertTrue(a.matchDestination("refs/remotes/mine/x-blah")); + assertTrue(a.matchDestination("refs/remotes/mine/foo-blah")); + assertTrue(a.matchDestination("refs/remotes/mine/foo/x-blah")); + assertFalse(a.matchDestination("refs/remotes/origin/foo/x-blah")); + + RefSpec b = a.expandFromSource("refs/heads/foo/for-linus"); + assertEquals("refs/remotes/mine/foo-blah", b.getDestination()); + RefSpec c = a.expandFromDestination("refs/remotes/mine/foo-blah"); + assertEquals("refs/heads/foo/for-linus", c.getSource()); + } + + @Test + public void testWildcardAfterText2() { + RefSpec a = new RefSpec("refs/heads*/for-linus:refs/remotes/mine/*"); + assertTrue(a.isWildcard()); + assertTrue(a.matchSource("refs/headsx/for-linus")); + assertTrue(a.matchSource("refs/headsfoo/for-linus")); + assertTrue(a.matchSource("refs/headsx/foo/for-linus")); + assertFalse(a.matchSource("refs/headx/for-linus")); + + RefSpec b = a.expandFromSource("refs/headsx/for-linus"); + assertEquals("refs/remotes/mine/x", b.getDestination()); + RefSpec c = a.expandFromDestination("refs/remotes/mine/x"); + assertEquals("refs/headsx/for-linus", c.getSource()); + + RefSpec d = a.expandFromSource("refs/headsx/foo/for-linus"); + assertEquals("refs/remotes/mine/x/foo", d.getDestination()); + RefSpec e = a.expandFromDestination("refs/remotes/mine/x/foo"); + assertEquals("refs/headsx/foo/for-linus", e.getSource()); + } + @Test public void testWildcardMirror() { RefSpec a = new RefSpec("*:*"); @@ -403,21 +438,6 @@ public void invalidWhenMoreThanOneWildcardInDestination() { assertNotNull(new RefSpec("refs/heads/*:refs/heads/*/*")); } - @Test(expected = IllegalArgumentException.class) - public void invalidWhenWildcardAfterText() { - assertNotNull(new RefSpec("refs/heads/wrong*:refs/heads/right/*")); - } - - @Test(expected = IllegalArgumentException.class) - public void invalidWhenWildcardBeforeText() { - assertNotNull(new RefSpec("*wrong:right/*")); - } - - @Test(expected = IllegalArgumentException.class) - public void invalidWhenWildcardBeforeTextAtEnd() { - assertNotNull(new RefSpec("refs/heads/*wrong:right/*")); - } - @Test(expected = IllegalArgumentException.class) public void invalidSourceDoubleSlashes() { assertNotNull(new RefSpec("refs/heads//wrong")); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java index 66ffc3abe..0e803bdaf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java @@ -457,10 +457,6 @@ private static boolean isValid(final String s) { if (i != -1) { if (s.indexOf('*', i + 1) > i) return false; - if (i > 0 && s.charAt(i - 1) != '/') - return false; - if (i < s.length() - 1 && s.charAt(i + 1) != '/') - return false; } return true; } From 82edf063117a42779a078361ab448f1da4ebc86d Mon Sep 17 00:00:00 2001 From: Marc Strapetz Date: Wed, 21 Oct 2015 12:03:53 +0200 Subject: [PATCH 03/89] BaseRepositoryBuilder should trim CR from .git symref Change-Id: I909c2892100da89f6670ffbf3442f11c9cb7b008 Signed-off-by: Marc Strapetz --- .../src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java index 45dd7ee1a..670f9a9e1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -109,7 +109,8 @@ private static File getSymRef(File workTree, File dotGit, FS fs) int pathStart = 8; int lineEnd = RawParseUtils.nextLF(content, pathStart); - if (content[lineEnd - 1] == '\n') + while (content[lineEnd - 1] == '\n' || + (content[lineEnd - 1] == '\r' && SystemReader.getInstance().isWindows())) lineEnd--; if (lineEnd == pathStart) throw new IOException(MessageFormat.format( From 5786cc3c0fb1afdd9942e9a353c6fc755a6b41d7 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Fri, 27 Nov 2015 11:23:42 +0100 Subject: [PATCH 04/89] Fix ChainingCredentialsProvider The ChainingCredentialsProvider gave up chaining to the next provider if the first one returned no credentials items for the given URI. Change-Id: I9539c50db35e564db9d43d8ebb71d7e9c6fdcc19 Signed-off-by: Matthias Sohn --- .../jgit/transport/ChainingCredentialsProvider.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java index 3e0ee2f64..5e1eb3c49 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java @@ -113,9 +113,15 @@ public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem { for (CredentialsProvider p : credentialProviders) { if (p.supports(items)) { - p.get(uri, items); - if (isAnyNull(items)) + if (!p.get(uri, items)) { + if (p.isInteractive()) { + return false; // user cancelled the request + } continue; + } + if (isAnyNull(items)) { + continue; + } return true; } } From afd167a1f26d140b7023972ddf85d12f3c9f9d8d Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Tue, 8 Dec 2015 14:11:43 +0100 Subject: [PATCH 05/89] NetRCCredentialsProvider should return false if any item is missing Change-Id: I894d1621aaccd71dfe100fe83a1bd9d50a1e0808 --- .../transport/ChainingCredentialsProvider.java | 7 ------- .../jgit/transport/CredentialsProvider.java | 14 ++++++++++++++ .../jgit/transport/NetRCCredentialsProvider.java | 3 +-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java index 5e1eb3c49..3941d3c55 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java @@ -127,11 +127,4 @@ public boolean get(URIish uri, CredentialItem... items) } return false; } - - private boolean isAnyNull(CredentialItem... items) { - for (CredentialItem i : items) - if (i == null) - return true; - return false; - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java index 464d0f9ee..4800f6826 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java @@ -80,6 +80,20 @@ public static void setDefault(CredentialsProvider p) { defaultProvider = p; } + /** + * @param items + * credential items to check + * @return {@code true} if any of the passed items is null, {@code false} + * otherwise + * @since 4.2 + */ + protected static boolean isAnyNull(CredentialItem... items) { + for (CredentialItem i : items) + if (i == null) + return true; + return false; + } + /** * Check if the provider is interactive with the end-user. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java index 74909998c..4037545e9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java @@ -105,12 +105,11 @@ public boolean get(URIish uri, CredentialItem... items) throw new UnsupportedCredentialItem(uri, i.getClass().getName() + ":" + i.getPromptText()); //$NON-NLS-1$ } - return true; + return !isAnyNull(items); } @Override public boolean isInteractive() { return false; } - } From 618b0c1ceb65fa26475e9706dd9a64a28ff788db Mon Sep 17 00:00:00 2001 From: Doug Kelly Date: Wed, 9 Dec 2015 16:36:37 -0600 Subject: [PATCH 06/89] Accept UTF8 BOM with BlobBasedConfig In I1f5dc07182dbf6bba2a9f4807fdd25b475da4ead, FileBasedConfig got support for reading a configuration with UTF8 BOM. Apply the same support to BlobBasedConfig, to make SubmoduleWalk able to parse .gitmodules configurations with BOM. Change-Id: I25b5474779952fe2c076180b96fc2869eef190a8 Signed-off-by: Doug Kelly --- .../src/org/eclipse/jgit/lib/BlobBasedConfig.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java index cbb2f5b85..7d52991df 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java @@ -79,7 +79,15 @@ public class BlobBasedConfig extends Config { public BlobBasedConfig(Config base, final byte[] blob) throws ConfigInvalidException { super(base); - fromText(RawParseUtils.decode(blob)); + final String decoded; + if (blob.length >= 3 && blob[0] == (byte) 0xEF + && blob[1] == (byte) 0xBB && blob[2] == (byte) 0xBF) { + decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET, + blob, 3, blob.length); + } else { + decoded = RawParseUtils.decode(blob); + } + fromText(decoded); } /** From 95b36b397b528f858ef1559da3c33c6cf3d7117b Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Sat, 28 Nov 2015 00:15:36 +0100 Subject: [PATCH 07/89] Null-annotated Ref class and fixed related compiler errors This change fixes all compiler errors in JGit and replaces possible NPE's with either appropriate exceptions, avoiding multiple "Nullable return" method calls or early returning from the method. Change-Id: I24c8a600ec962d61d5f40abf73eac4203e115240 Signed-off-by: Andrey Loskutov --- .../src/org/eclipse/jgit/pgm/Merge.java | 9 ++-- .../src/org/eclipse/jgit/pgm/RevParse.java | 8 ++- .../eclipse/jgit/internal/JGitText.properties | 2 + .../org/eclipse/jgit/api/CheckoutCommand.java | 13 +++-- .../org/eclipse/jgit/api/CloneCommand.java | 13 +++-- .../org/eclipse/jgit/api/RebaseCommand.java | 19 +++++-- .../org/eclipse/jgit/internal/JGitText.java | 2 + .../storage/dfs/InMemoryRepository.java | 13 +++-- .../jgit/internal/storage/file/GC.java | 6 ++- .../internal/storage/file/RefDirectory.java | 49 +++++++++++++------ .../src/org/eclipse/jgit/lib/ObjectIdRef.java | 36 ++++++++++---- .../src/org/eclipse/jgit/lib/Ref.java | 17 +++++-- .../src/org/eclipse/jgit/lib/RefWriter.java | 26 +++++++--- .../src/org/eclipse/jgit/lib/Repository.java | 12 +++-- .../src/org/eclipse/jgit/lib/SymbolicRef.java | 11 ++++- .../jgit/merge/MergeMessageFormatter.java | 23 ++++----- .../transport/BasePackFetchConnection.java | 8 ++- .../transport/BasePackPushConnection.java | 7 ++- .../jgit/transport/BaseReceivePack.java | 34 ++++++++----- .../eclipse/jgit/transport/FetchProcess.java | 15 +++++- .../eclipse/jgit/transport/PushProcess.java | 9 +++- .../jgit/transport/WalkFetchConnection.java | 4 ++ 22 files changed, 244 insertions(+), 92 deletions(-) diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java index cd65af954..e739b58ae 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java @@ -148,9 +148,12 @@ protected void run() throws Exception { break; case FAST_FORWARD: ObjectId oldHeadId = oldHead.getObjectId(); - outw.println(MessageFormat.format(CLIText.get().updating, oldHeadId - .abbreviate(7).name(), result.getNewHead().abbreviate(7) - .name())); + if (oldHeadId != null) { + String oldId = oldHeadId.abbreviate(7).name(); + String newId = result.getNewHead().abbreviate(7).name(); + outw.println(MessageFormat.format(CLIText.get().updating, oldId, + newId)); + } outw.println(result.getMergeStatus().toString()); break; case CHECKOUT_CONFLICT: diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java index 4456fd534..939f5836f 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java @@ -74,7 +74,13 @@ protected void run() throws Exception { if (all) { Map allRefs = db.getRefDatabase().getRefs(ALL); for (final Ref r : allRefs.values()) { - outw.println(r.getObjectId().name()); + ObjectId objectId = r.getObjectId(); + // getRefs skips dangling symrefs, so objectId should never be + // null. + if (objectId == null) { + throw new NullPointerException(); + } + outw.println(objectId.name()); } } else { if (verify && commits.size() > 1) { diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 0e9b0b59e..840059d34 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -99,6 +99,7 @@ cannotSquashFixupWithoutPreviousCommit=Cannot {0} without previous commit. cannotStoreObjects=cannot store objects cannotResolveUniquelyAbbrevObjectId=Could not resolve uniquely the abbreviated object ID cannotUnloadAModifiedTree=Cannot unload a modified tree. +cannotUpdateUnbornBranch=Cannot update unborn branch cannotWorkWithOtherStagesThanZeroRightNow=Cannot work with other stages than zero right now. Won't write corrupt index. cannotWriteObjectsPath=Cannot write {0}/{1}: {2} canOnlyCherryPickCommitsWithOneParent=Cannot cherry-pick commit ''{0}'' because it has {1} parents, only commits with exactly one parent are supported. @@ -595,6 +596,7 @@ transportExceptionInvalid=Invalid {0} {1}:{2} transportExceptionMissingAssumed=Missing assumed {0} transportExceptionReadRef=read {0} transportNeedsRepository=Transport needs repository +transportProvidedRefWithNoObjectId=Transport provided ref {0} with no object id transportProtoAmazonS3=Amazon S3 transportProtoBundleFile=Git Bundle File transportProtoFTP=FTP 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 8743ea9ac..4f918fa35 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -331,9 +331,16 @@ else if (orphan) { } private String getShortBranchName(Ref headRef) { - if (headRef.getTarget().getName().equals(headRef.getName())) - return headRef.getTarget().getObjectId().getName(); - return Repository.shortenRefName(headRef.getTarget().getName()); + if (headRef.isSymbolic()) { + return Repository.shortenRefName(headRef.getTarget().getName()); + } + // Detached HEAD. Every non-symbolic ref in the ref database has an + // object id, so this cannot be null. + ObjectId id = headRef.getObjectId(); + if (id == null) { + throw new NullPointerException(); + } + return id.getName(); } /** 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 b3bc319ae..2ac872950 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java @@ -61,6 +61,7 @@ import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; @@ -235,7 +236,7 @@ private void checkout(Repository clonedRepo, FetchResult result) } if (head == null || head.getObjectId() == null) - return; // throw exception? + return; // TODO throw exception? if (head.getName().startsWith(Constants.R_HEADS)) { final RefUpdate newHead = clonedRepo.updateRef(Constants.HEAD); @@ -287,20 +288,24 @@ private void cloneSubmodules(Repository clonedRepo) throws IOException, private Ref findBranchToCheckout(FetchResult result) { final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD); - if (idHEAD == null) + ObjectId headId = idHEAD != null ? idHEAD.getObjectId() : null; + if (headId == null) { return null; + } Ref master = result.getAdvertisedRef(Constants.R_HEADS + Constants.MASTER); - if (master != null && master.getObjectId().equals(idHEAD.getObjectId())) + ObjectId objectId = master != null ? master.getObjectId() : null; + if (headId.equals(objectId)) { return master; + } Ref foundBranch = null; for (final Ref r : result.getAdvertisedRefs()) { final String n = r.getName(); if (!n.startsWith(Constants.R_HEADS)) continue; - if (r.getObjectId().equals(idHEAD.getObjectId())) { + if (headId.equals(r.getObjectId())) { foundBranch = r; break; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java index 8582bbb0d..e3e76c95f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -560,6 +560,8 @@ private RebaseResult cherryPickCommitPreservingMerges(RevCommit commitToPick) lastStepWasForward = newHead != null; if (!lastStepWasForward) { ObjectId headId = getHead().getObjectId(); + // getHead() checks for null + assert headId != null; if (!AnyObjectId.equals(headId, newParents.get(0))) checkoutCommit(headId.getName(), newParents.get(0)); @@ -674,6 +676,8 @@ private void writeRewrittenHashes() throws RevisionSyntaxException, return; ObjectId headId = getHead().getObjectId(); + // getHead() checks for null + assert headId != null; String head = headId.getName(); String currentCommits = rebaseState.readFile(CURRENT_COMMIT); for (String current : currentCommits.split("\n")) //$NON-NLS-1$ @@ -1073,11 +1077,12 @@ private RebaseResult initFilesAndRewind() throws IOException, Ref head = getHead(); - String headName = getHeadName(head); ObjectId headId = head.getObjectId(); - if (headId == null) + if (headId == null) { throw new RefNotFoundException(MessageFormat.format( JGitText.get().refNotResolved, Constants.HEAD)); + } + String headName = getHeadName(head); RevCommit headCommit = walk.lookupCommit(headId); RevCommit upstream = walk.lookupCommit(upstreamCommit.getId()); @@ -1188,10 +1193,14 @@ private List calculatePickList(RevCommit headCommit) private static String getHeadName(Ref head) { String headName; - if (head.isSymbolic()) + if (head.isSymbolic()) { headName = head.getTarget().getName(); - else - headName = head.getObjectId().getName(); + } else { + ObjectId headId = head.getObjectId(); + // the callers are checking this already + assert headId != null; + headName = headId.getName(); + } return headName; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 796eaaebf..c476f1773 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -158,6 +158,7 @@ public static JGitText get() { /***/ public String cannotStoreObjects; /***/ public String cannotResolveUniquelyAbbrevObjectId; /***/ public String cannotUnloadAModifiedTree; + /***/ public String cannotUpdateUnbornBranch; /***/ public String cannotWorkWithOtherStagesThanZeroRightNow; /***/ public String cannotWriteObjectsPath; /***/ public String canOnlyCherryPickCommitsWithOneParent; @@ -663,6 +664,7 @@ public static JGitText get() { /***/ public String transportProtoSFTP; /***/ public String transportProtoSSH; /***/ public String transportProtoTest; + /***/ public String transportProvidedRefWithNoObjectId; /***/ public String transportSSHRetryInterrupt; /***/ public String treeEntryAlreadyExists; /***/ public String treeFilterMarkerTooManyFilters; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java index 1c664b409..cdebb7a8f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java @@ -334,11 +334,14 @@ private void batch(RevWalk walk, List cmds) { reject(cmds); return; } - } else if (r.isSymbolic() || r.getObjectId() == null - || !r.getObjectId().equals(c.getOldId())) { - c.setResult(ReceiveCommand.Result.LOCK_FAILURE); - reject(cmds); - return; + } else { + ObjectId objectId = r.getObjectId(); + if (r.isSymbolic() || objectId == null + || !objectId.equals(c.getOldId())) { + c.setResult(ReceiveCommand.Result.LOCK_FAILURE); + reject(cmds); + return; + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index 4c40538b6..c0f56f448 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -67,6 +67,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import java.util.TreeMap; @@ -483,9 +484,10 @@ private static boolean equals(Ref r1, Ref r2) { return false; return r1.getTarget().getName().equals(r2.getTarget().getName()); } else { - if (r2.isSymbolic()) + if (r2.isSymbolic()) { return false; - return r1.getObjectId().equals(r2.getObjectId()); + } + return Objects.equals(r1.getObjectId(), r2.getObjectId()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java index 69f7e9707..2c8e5f9d1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java @@ -73,6 +73,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.errors.InvalidObjectIdException; import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.errors.MissingObjectException; @@ -715,16 +716,20 @@ public void pack(List refs) throws IOException { */ private Ref peeledPackedRef(Ref f) throws MissingObjectException, IOException { - if (f.getStorage().isPacked() && f.isPeeled()) + if (f.getStorage().isPacked() && f.isPeeled()) { return f; - if (!f.isPeeled()) + } + if (!f.isPeeled()) { f = peel(f); - if (f.getPeeledObjectId() != null) + } + ObjectId peeledObjectId = f.getPeeledObjectId(); + if (peeledObjectId != null) { return new ObjectIdRef.PeeledTag(PACKED, f.getName(), - f.getObjectId(), f.getPeeledObjectId()); - else + f.getObjectId(), peeledObjectId); + } else { return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(), f.getObjectId()); + } } void log(final RefUpdate update, final String msg, final boolean deref) @@ -985,7 +990,7 @@ LooseRef scanRef(LooseRef ref, String name) throws IOException { try { id = ObjectId.fromString(buf, 0); if (ref != null && !ref.isSymbolic() - && ref.getTarget().getObjectId().equals(id)) { + && id.equals(ref.getTarget().getObjectId())) { assert(currentSnapshot != null); currentSnapshot.setClean(otherSnapshot); return ref; @@ -1103,8 +1108,8 @@ private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag implements LooseRef { private final FileSnapshot snapShot; - LoosePeeledTag(FileSnapshot snapshot, String refName, ObjectId id, - ObjectId p) { + LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName, + @NonNull ObjectId id, @NonNull ObjectId p) { super(LOOSE, refName, id, p); this.snapShot = snapshot; } @@ -1122,7 +1127,8 @@ private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag implements LooseRef { private final FileSnapshot snapShot; - LooseNonTag(FileSnapshot snapshot, String refName, ObjectId id) { + LooseNonTag(FileSnapshot snapshot, @NonNull String refName, + @NonNull ObjectId id) { super(LOOSE, refName, id); this.snapShot = snapshot; } @@ -1140,7 +1146,8 @@ private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled implements LooseRef { private FileSnapshot snapShot; - LooseUnpeeled(FileSnapshot snapShot, String refName, ObjectId id) { + LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName, + @NonNull ObjectId id) { super(LOOSE, refName, id); this.snapShot = snapShot; } @@ -1149,13 +1156,24 @@ public FileSnapshot getSnapShot() { return snapShot; } + @NonNull + @Override + public ObjectId getObjectId() { + ObjectId id = super.getObjectId(); + assert id != null; // checked in constructor + return id; + } + public LooseRef peel(ObjectIdRef newLeaf) { - if (newLeaf.getPeeledObjectId() != null) + ObjectId peeledObjectId = newLeaf.getPeeledObjectId(); + ObjectId objectId = getObjectId(); + if (peeledObjectId != null) { return new LoosePeeledTag(snapShot, getName(), - getObjectId(), newLeaf.getPeeledObjectId()); - else + objectId, peeledObjectId); + } else { return new LooseNonTag(snapShot, getName(), - getObjectId()); + objectId); + } } } @@ -1163,7 +1181,8 @@ private final static class LooseSymbolicRef extends SymbolicRef implements LooseRef { private final FileSnapshot snapShot; - LooseSymbolicRef(FileSnapshot snapshot, String refName, Ref target) { + LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName, + @NonNull Ref target) { super(refName, target); this.snapShot = snapshot; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java index f481c772d..c286f5e46 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java @@ -44,6 +44,9 @@ package org.eclipse.jgit.lib; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; + /** A {@link Ref} that points directly at an {@link ObjectId}. */ public abstract class ObjectIdRef implements Ref { /** Any reference whose peeled value is not yet known. */ @@ -56,13 +59,15 @@ public static class Unpeeled extends ObjectIdRef { * @param name * name of this ref. * @param id - * current value of the ref. May be null to indicate a ref - * that does not exist yet. + * current value of the ref. May be {@code null} to indicate + * a ref that does not exist yet. */ - public Unpeeled(Storage st, String name, ObjectId id) { + public Unpeeled(@NonNull Storage st, @NonNull String name, + @Nullable ObjectId id) { super(st, name, id); } + @Nullable public ObjectId getPeeledObjectId() { return null; } @@ -88,11 +93,13 @@ public static class PeeledTag extends ObjectIdRef { * @param p * the first non-tag object that tag {@code id} points to. */ - public PeeledTag(Storage st, String name, ObjectId id, ObjectId p) { + public PeeledTag(@NonNull Storage st, @NonNull String name, + @Nullable ObjectId id, @NonNull ObjectId p) { super(st, name, id); peeledObjectId = p; } + @NonNull public ObjectId getPeeledObjectId() { return peeledObjectId; } @@ -112,13 +119,15 @@ public static class PeeledNonTag extends ObjectIdRef { * @param name * name of this ref. * @param id - * current value of the ref. May be null to indicate a ref - * that does not exist yet. + * current value of the ref. May be {@code null} to indicate + * a ref that does not exist yet. */ - public PeeledNonTag(Storage st, String name, ObjectId id) { + public PeeledNonTag(@NonNull Storage st, @NonNull String name, + @Nullable ObjectId id) { super(st, name, id); } + @Nullable public ObjectId getPeeledObjectId() { return null; } @@ -142,15 +151,17 @@ public boolean isPeeled() { * @param name * name of this ref. * @param id - * current value of the ref. May be null to indicate a ref that - * does not exist yet. + * current value of the ref. May be {@code null} to indicate a + * ref that does not exist yet. */ - protected ObjectIdRef(Storage st, String name, ObjectId id) { + protected ObjectIdRef(@NonNull Storage st, @NonNull String name, + @Nullable ObjectId id) { this.name = name; this.storage = st; this.objectId = id; } + @NonNull public String getName() { return name; } @@ -159,22 +170,27 @@ public boolean isSymbolic() { return false; } + @NonNull public Ref getLeaf() { return this; } + @NonNull public Ref getTarget() { return this; } + @Nullable public ObjectId getObjectId() { return objectId; } + @NonNull public Storage getStorage() { return storage; } + @NonNull @Override public String toString() { StringBuilder r = new StringBuilder(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java index f119c44fe..a78a90fe5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java @@ -43,6 +43,9 @@ package org.eclipse.jgit.lib; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; + /** * Pairing of a name and the {@link ObjectId} it currently has. *

@@ -126,6 +129,7 @@ public boolean isPacked() { * * @return name of this ref. */ + @NonNull public String getName(); /** @@ -156,6 +160,7 @@ public boolean isPacked() { * * @return the reference that actually stores the ObjectId value. */ + @NonNull public abstract Ref getLeaf(); /** @@ -170,22 +175,27 @@ public boolean isPacked() { * * @return the target reference, or {@code this}. */ + @NonNull public abstract Ref getTarget(); /** * Cached value of this ref. * - * @return the value of this ref at the last time we read it. + * @return the value of this ref at the last time we read it. May be + * {@code null} to indicate a ref that does not exist yet or a + * symbolic ref pointing to an unborn branch. */ + @Nullable public abstract ObjectId getObjectId(); /** * Cached value of ref^{} (the ref peeled to commit). * * @return if this ref is an annotated tag the id of the commit (or tree or - * blob) that the annotated tag refers to; null if this ref does not - * refer to an annotated tag. + * blob) that the annotated tag refers to; {@code null} if this ref + * does not refer to an annotated tag. */ + @Nullable public abstract ObjectId getPeeledObjectId(); /** @@ -201,5 +211,6 @@ public boolean isPacked() { * * @return type of ref. */ + @NonNull public abstract Storage getStorage(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java index 747fa62b5..3a02b2281 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java @@ -119,13 +119,20 @@ public void writeInfoRefs() throws IOException { continue; } - r.getObjectId().copyTo(tmp, w); + ObjectId objectId = r.getObjectId(); + if (objectId == null) { + // Symrefs to unborn branches aren't advertised in the info/refs + // file. + continue; + } + objectId.copyTo(tmp, w); w.write('\t'); w.write(r.getName()); w.write('\n'); - if (r.getPeeledObjectId() != null) { - r.getPeeledObjectId().copyTo(tmp, w); + ObjectId peeledObjectId = r.getPeeledObjectId(); + if (peeledObjectId != null) { + peeledObjectId.copyTo(tmp, w); w.write('\t'); w.write(r.getName()); w.write("^{}\n"); //$NON-NLS-1$ @@ -167,14 +174,21 @@ public void writePackedRefs() throws IOException { if (r.getStorage() != Ref.Storage.PACKED) continue; - r.getObjectId().copyTo(tmp, w); + ObjectId objectId = r.getObjectId(); + if (objectId == null) { + // A packed ref cannot be a symref, let alone a symref + // to an unborn branch. + throw new NullPointerException(); + } + objectId.copyTo(tmp, w); w.write(' '); w.write(r.getName()); w.write('\n'); - if (r.getPeeledObjectId() != null) { + ObjectId peeledObjectId = r.getPeeledObjectId(); + if (peeledObjectId != null) { w.write('^'); - r.getPeeledObjectId().copyTo(tmp, w); + peeledObjectId.copyTo(tmp, w); w.write('\n'); } } 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 49a970d03..f8266133a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -911,12 +911,16 @@ public String toString() { @Nullable public String getFullBranch() throws IOException { Ref head = getRef(Constants.HEAD); - if (head == null) + if (head == null) { return null; - if (head.isSymbolic()) + } + if (head.isSymbolic()) { return head.getTarget().getName(); - if (head.getObjectId() != null) - return head.getObjectId().name(); + } + ObjectId objectId = head.getObjectId(); + if (objectId != null) { + return objectId.name(); + } return null; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java index 43b1510f9..eeab921a7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java @@ -43,6 +43,9 @@ package org.eclipse.jgit.lib; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; + /** * A reference that indirectly points at another {@link Ref}. *

@@ -62,11 +65,12 @@ public class SymbolicRef implements Ref { * @param target * the ref we reference and derive our value from. */ - public SymbolicRef(String refName, Ref target) { + public SymbolicRef(@NonNull String refName, @NonNull Ref target) { this.name = refName; this.target = target; } + @NonNull public String getName() { return name; } @@ -75,6 +79,7 @@ public boolean isSymbolic() { return true; } + @NonNull public Ref getLeaf() { Ref dst = getTarget(); while (dst.isSymbolic()) @@ -82,18 +87,22 @@ public Ref getLeaf() { return dst; } + @NonNull public Ref getTarget() { return target; } + @Nullable public ObjectId getObjectId() { return getLeaf().getObjectId(); } + @NonNull public Storage getStorage() { return Storage.LOOSE; } + @Nullable public ObjectId getPeeledObjectId() { return getLeaf().getPeeledObjectId(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java index 191f3d836..82cbf368c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java @@ -46,6 +46,7 @@ import java.util.List; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.ChangeIdUtil; @@ -76,22 +77,22 @@ public String format(List refsToMerge, Ref target) { List commits = new ArrayList(); List others = new ArrayList(); for (Ref ref : refsToMerge) { - if (ref.getName().startsWith(Constants.R_HEADS)) + if (ref.getName().startsWith(Constants.R_HEADS)) { branches.add("'" + Repository.shortenRefName(ref.getName()) //$NON-NLS-1$ + "'"); //$NON-NLS-1$ - - else if (ref.getName().startsWith(Constants.R_REMOTES)) + } else if (ref.getName().startsWith(Constants.R_REMOTES)) { remoteBranches.add("'" //$NON-NLS-1$ + Repository.shortenRefName(ref.getName()) + "'"); //$NON-NLS-1$ - - else if (ref.getName().startsWith(Constants.R_TAGS)) + } else if (ref.getName().startsWith(Constants.R_TAGS)) { tags.add("'" + Repository.shortenRefName(ref.getName()) + "'"); //$NON-NLS-1$ //$NON-NLS-2$ - - else if (ref.getName().equals(ref.getObjectId().getName())) - commits.add("'" + ref.getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ - - else - others.add(ref.getName()); + } else { + ObjectId objectId = ref.getObjectId(); + if (objectId != null && ref.getName().equals(objectId.getName())) { + commits.add("'" + ref.getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + others.add(ref.getName()); + } + } } List listings = new ArrayList(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java index cf13582db..754cf361a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java @@ -464,8 +464,12 @@ private boolean sendWants(final Collection want) throws IOException { final PacketLineOut p = statelessRPC ? pckState : pckOut; boolean first = true; for (final Ref r : want) { + ObjectId objectId = r.getObjectId(); + if (objectId == null) { + continue; + } try { - if (walk.parseAny(r.getObjectId()).has(REACHABLE)) { + if (walk.parseAny(objectId).has(REACHABLE)) { // We already have this object. Asking for it is // not a very good idea. // @@ -478,7 +482,7 @@ private boolean sendWants(final Collection want) throws IOException { final StringBuilder line = new StringBuilder(46); line.append("want "); //$NON-NLS-1$ - line.append(r.getObjectId().name()); + line.append(objectId.name()); if (first) { line.append(enableCapabilities()); first = false; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java index 0834c359a..4499f66d5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java @@ -239,8 +239,11 @@ private void writeCommands(final Collection refUpdates, final StringBuilder sb = new StringBuilder(); ObjectId oldId = rru.getExpectedOldObjectId(); if (oldId == null) { - Ref adv = getRef(rru.getRemoteName()); - oldId = adv != null ? adv.getObjectId() : ObjectId.zeroId(); + final Ref advertised = getRef(rru.getRemoteName()); + oldId = advertised != null ? advertised.getObjectId() : null; + if (oldId == null) { + oldId = ObjectId.zeroId(); + } } sb.append(oldId.name()); sb.append(' '); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java index 776a9f695..c224d8eeb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java @@ -1372,16 +1372,21 @@ protected void validateCommands() { } } - if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null - && !ObjectId.zeroId().equals(cmd.getOldId()) - && !ref.getObjectId().equals(cmd.getOldId())) { - // Delete commands can be sent with the old id matching our - // advertised value, *OR* with the old id being 0{40}. Any - // other requested old id is invalid. - // - cmd.setResult(Result.REJECTED_OTHER_REASON, - JGitText.get().invalidOldIdSent); - continue; + if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null) { + ObjectId id = ref.getObjectId(); + if (id == null) { + id = ObjectId.zeroId(); + } + if (!ObjectId.zeroId().equals(cmd.getOldId()) + && !id.equals(cmd.getOldId())) { + // Delete commands can be sent with the old id matching our + // advertised value, *OR* with the old id being 0{40}. Any + // other requested old id is invalid. + // + cmd.setResult(Result.REJECTED_OTHER_REASON, + JGitText.get().invalidOldIdSent); + continue; + } } if (cmd.getType() == ReceiveCommand.Type.UPDATE) { @@ -1391,8 +1396,15 @@ protected void validateCommands() { cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().noSuchRef); continue; } + ObjectId id = ref.getObjectId(); + if (id == null) { + // We cannot update unborn branch + cmd.setResult(Result.REJECTED_OTHER_REASON, + JGitText.get().cannotUpdateUnbornBranch); + continue; + } - if (!ref.getObjectId().equals(cmd.getOldId())) { + if (!id.equals(cmd.getOldId())) { // A properly functioning client will send the same // object id we advertised. // diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java index 9aae1c37a..c4b3f8304 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -397,11 +397,17 @@ private Collection expandAutoFollowTags() throws TransportException { private void expandFetchTags() throws TransportException { final Map haveRefs = localRefs(); for (final Ref r : conn.getRefs()) { - if (!isTag(r)) + if (!isTag(r)) { continue; + } + ObjectId id = r.getObjectId(); + if (id == null) { + continue; + } final Ref local = haveRefs.get(r.getName()); - if (local == null || !r.getObjectId().equals(local.getObjectId())) + if (local == null || !id.equals(local.getObjectId())) { wantTag(r); + } } } @@ -413,6 +419,11 @@ private void wantTag(final Ref r) throws TransportException { private void want(final Ref src, final RefSpec spec) throws TransportException { final ObjectId newId = src.getObjectId(); + if (newId == null) { + throw new NullPointerException(MessageFormat.format( + JGitText.get().transportProvidedRefWithNoObjectId, + src.getName())); + } if (spec.getDestination() != null) { final TrackingRefUpdate tru = createUpdate(spec, newId); if (newId.equals(tru.getOldObjectId())) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java index 4fd192dbb..5cea88215 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java @@ -188,8 +188,13 @@ private Map prepareRemoteUpdates() final Map result = new HashMap(); for (final RemoteRefUpdate rru : toPush.values()) { final Ref advertisedRef = connection.getRef(rru.getRemoteName()); - final ObjectId advertisedOld = (advertisedRef == null ? ObjectId - .zeroId() : advertisedRef.getObjectId()); + ObjectId advertisedOld = null; + if (advertisedRef != null) { + advertisedOld = advertisedRef.getObjectId(); + } + if (advertisedOld == null) { + advertisedOld = ObjectId.zeroId(); + } if (rru.getNewObjectId().equals(advertisedOld)) { if (rru.isDelete()) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java index 1c6b8b736..dfc3ee4c3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -267,6 +267,10 @@ private void queueWants(final Collection want) final HashSet inWorkQueue = new HashSet(); for (final Ref r : want) { final ObjectId id = r.getObjectId(); + if (id == null) { + throw new NullPointerException(MessageFormat.format( + JGitText.get().transportProvidedRefWithNoObjectId, r.getName())); + } try { final RevObject obj = revWalk.parseAny(id); if (obj.has(COMPLETE)) From 310e858f818405d6ad4b9758f22abebd26d0ea88 Mon Sep 17 00:00:00 2001 From: Christian Halstrick Date: Tue, 15 Dec 2015 09:24:07 +0100 Subject: [PATCH 08/89] Fix possible arithmetic overflow when setting a timeout BasePackPushConnection#readStringLongTimeout() was setting a timeout 10 times bigger than some other timeout or the pack transfer time. This could lead to negative integer values when we hit an arithmetic overflow. Add a check for this situation and set the timeout to Integer.MAX_VALUE when overflow happens. Bug: 484352 CC: Eugene Petrenko Change-Id: Ie2a86312c1bcb1ec3e6388fa490ab3c845d41808 --- .../src/org/eclipse/jgit/transport/BasePackPushConnection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java index 4499f66d5..963de35d4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java @@ -385,7 +385,8 @@ private String readStringLongTimeout() throws IOException { final int oldTimeout = timeoutIn.getTimeout(); final int sendTime = (int) Math.min(packTransferTime, 28800000L); try { - timeoutIn.setTimeout(10 * Math.max(sendTime, oldTimeout)); + int timeout = 10 * Math.max(sendTime, oldTimeout); + timeoutIn.setTimeout((timeout < 0) ? Integer.MAX_VALUE : timeout); return pckIn.readString(); } finally { timeoutIn.setTimeout(oldTimeout); From 741829f1775a199514af7ca05d35ba39fd934668 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Fri, 27 Nov 2015 11:26:38 +0100 Subject: [PATCH 09/89] Enable retrieval of credentials from .netrc for AwtCredentialsProvider This was done for ConsoleCredentialsProvider earlier, we need the AwtCredentialsProvider for debugging jgit command line since there is no console in Eclipse. Hence also add support for .netrc here. Change-Id: Ibbd45b73efc663821866754454cea65e6d03f832 Signed-off-by: Matthias Sohn --- .../src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java index fd26bfa7f..a9967ae49 100644 --- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java +++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java @@ -56,15 +56,20 @@ import javax.swing.JTextField; import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.transport.ChainingCredentialsProvider; import org.eclipse.jgit.transport.CredentialItem; import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.NetRCCredentialsProvider; import org.eclipse.jgit.transport.URIish; /** Interacts with the user during authentication by using AWT/Swing dialogs. */ public class AwtCredentialsProvider extends CredentialsProvider { /** Install this implementation as the default. */ public static void install() { - CredentialsProvider.setDefault(new AwtCredentialsProvider()); + final AwtCredentialsProvider c = new AwtCredentialsProvider(); + CredentialsProvider cp = new ChainingCredentialsProvider( + new NetRCCredentialsProvider(), c); + CredentialsProvider.setDefault(cp); } @Override From 2647d024afe7aac427ab37bba1655f2e54b257d5 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Fri, 27 Nov 2015 11:46:21 +0100 Subject: [PATCH 10/89] Fix push with jgit pgm failing with "unauthorized" Pushing with JGit commandline to e.g. Github failed with "unauthorized" since HttpUrlConnection calls the configured authenticator implicitly. The problem is that during a push two requests are sent to the server, first a GET and then a POST (containing the pack data). The first GET request sent anonymously is rejected with 401 (unauthorized). When an Authenticator is installed the java.net classes will use the Authenticator to ask the user for credentials and retry the request. But this happens under the hood and JGit level code doesn't see that this happens. The next request is the POST but since JGit thinks the first GET request went through anonymously it doesn't add authentication headers to the POST request. This POST of course also fails with 401 but since this request contains a lot of body-data streamed from JGit (the pack file!) the java.net classes can't simply retry the request with authorization headers. The whole process fails. Fix this by using Apache httpclient which doesn't use Authenticator to retrieve credentials. Instead initialize TransportCommand to use the default credential provider if no other credentials provider was set explicitly. org.eclipse.jgit.pgm.Main sets this default for the JGit command line client. Change-Id: Ic4e0f8b60d4bd6e69d91eae0c7e1b44cdf851b00 Signed-off-by: Matthias Sohn --- org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF | 3 ++- org.eclipse.jgit.pgm/META-INF/MANIFEST.MF | 4 +++- org.eclipse.jgit.pgm/pom.xml | 11 +++++++++++ .../src/org/eclipse/jgit/pgm/Main.java | 3 +++ .../src/org/eclipse/jgit/api/TransportCommand.java | 1 + 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF index 07f364c53..8058f309f 100644 --- a/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.http.apache/META-INF/MANIFEST.MF @@ -7,7 +7,8 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-Localization: plugin Bundle-Vendor: %Provider-Name Bundle-ActivationPolicy: lazy -Import-Package: org.apache.http;version="[4.1.0,5.0.0)", +Import-Package: org.apache.commons.logging;version="[1.1.1,2.0.0)", + org.apache.http;version="[4.1.0,5.0.0)", org.apache.http.client;version="[4.1.0,5.0.0)", org.apache.http.client.methods;version="[4.1.0,5.0.0)", org.apache.http.client.params;version="[4.1.0,5.0.0)", diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index 567fd0575..bd09e4e74 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -7,7 +7,8 @@ Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)", +Import-Package: org.apache.commons.codec.binary;version="[1.6.0,2.0.0)", + org.apache.commons.compress.archivers;version="[1.3,2.0)", org.apache.commons.compress.archivers.tar;version="[1.3,2.0)", org.apache.commons.compress.archivers.zip;version="[1.3,2.0)", org.eclipse.jgit.api;version="[4.2.0,4.3.0)", @@ -31,6 +32,7 @@ Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)", org.eclipse.jgit.storage.file;version="[4.2.0,4.3.0)", org.eclipse.jgit.storage.pack;version="[4.2.0,4.3.0)", org.eclipse.jgit.transport;version="[4.2.0,4.3.0)", + org.eclipse.jgit.transport.http.apache;version="[4.2.0,4.3.0)", org.eclipse.jgit.transport.resolver;version="[4.2.0,4.3.0)", org.eclipse.jgit.treewalk;version="[4.2.0,4.3.0)", org.eclipse.jgit.treewalk.filter;version="[4.2.0,4.3.0)", diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml index ca2ead292..264249132 100644 --- a/org.eclipse.jgit.pgm/pom.xml +++ b/org.eclipse.jgit.pgm/pom.xml @@ -94,6 +94,17 @@ ${project.version} + + org.eclipse.jgit + org.eclipse.jgit.http.apache + ${project.version} + + + + org.apache.httpcomponents + httpclient + + org.slf4j slf4j-api diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java index ceb0d6b2f..9c72b4aad 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java @@ -62,6 +62,8 @@ import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.pgm.opt.CmdLineParser; import org.eclipse.jgit.pgm.opt.SubcommandHandler; +import org.eclipse.jgit.transport.HttpTransport; +import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory; import org.eclipse.jgit.util.CachedAuthenticator; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; @@ -95,6 +97,7 @@ public class Main { * arguments. */ public static void main(final String[] argv) { + HttpTransport.setConnectionFactory(new HttpClientConnectionFactory()); new Main().run(argv); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java index 1aeb6109e..3d2e46b26 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java @@ -79,6 +79,7 @@ public abstract class TransportCommand extends */ protected TransportCommand(final Repository repo) { super(repo); + setCredentialsProvider(CredentialsProvider.getDefault()); } /** From 3e03907f650a5e1e6f8714ed09ee074f9d202540 Mon Sep 17 00:00:00 2001 From: Mike Gilbode Date: Tue, 15 Dec 2015 01:59:00 -0500 Subject: [PATCH 11/89] Bug 484342: Support @ in username in SSH url. Change-Id: I5795e925afff796488ba26c83694e806b76a374f Signed-off-by: Mike Gilbode --- .../eclipse/jgit/transport/URIishTest.java | 42 +++++++++++++++++++ .../org/eclipse/jgit/transport/URIish.java | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java index 76eb18afd..5e4ba3507 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java @@ -468,6 +468,48 @@ public void testSshProtoWithUserPassAndPort() throws Exception { assertEquals(u, new URIish(str)); } + @Test + public void testSshProtoWithEmailUserAndPort() throws Exception { + final String str = "ssh://user.name@email.com@example.com:33/some/p ath"; + URIish u = new URIish(str); + assertEquals("ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/some/p ath", u.getRawPath()); + assertEquals("/some/p ath", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals("user.name@email.com", u.getUser()); + assertNull(u.getPass()); + assertEquals(33, u.getPort()); + assertEquals("ssh://user.name%40email.com@example.com:33/some/p ath", + u.toPrivateString()); + assertEquals("ssh://user.name%40email.com@example.com:33/some/p%20ath", + u.toPrivateASCIIString()); + assertEquals(u.setPass(null).toPrivateString(), u.toString()); + assertEquals(u.setPass(null).toPrivateASCIIString(), u.toASCIIString()); + assertEquals(u, new URIish(str)); + } + + @Test + public void testSshProtoWithEmailUserPassAndPort() throws Exception { + final String str = "ssh://user.name@email.com:pass@wor:d@example.com:33/some/p ath"; + URIish u = new URIish(str); + assertEquals("ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/some/p ath", u.getRawPath()); + assertEquals("/some/p ath", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals("user.name@email.com", u.getUser()); + assertEquals("pass@wor:d", u.getPass()); + assertEquals(33, u.getPort()); + assertEquals("ssh://user.name%40email.com:pass%40wor%3ad@example.com:33/some/p ath", + u.toPrivateString()); + assertEquals("ssh://user.name%40email.com:pass%40wor%3ad@example.com:33/some/p%20ath", + u.toPrivateASCIIString()); + assertEquals(u.setPass(null).toPrivateString(), u.toString()); + assertEquals(u.setPass(null).toPrivateASCIIString(), u.toASCIIString()); + assertEquals(u, new URIish(str)); + } + @Test public void testSshProtoWithADUserPassAndPort() throws Exception { final String str = "ssh://DOMAIN\\user:pass@example.com:33/some/p ath"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java index 9aeb840eb..5ce39c2e0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java @@ -83,7 +83,7 @@ public class URIish implements Serializable { * capturing groups: the first containing the user and the second containing * the password */ - private static final String OPT_USER_PWD_P = "(?:([^/:@]+)(?::([^\\\\/]+))?@)?"; //$NON-NLS-1$ + private static final String OPT_USER_PWD_P = "(?:([^/:]+)(?::([^\\\\/]+))?@)?"; //$NON-NLS-1$ /** * Part of a pattern which matches the host part of URIs. Defines one From 29dfbd22b7250d1a7779ba4fe00323fd167e35fc Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 14 Dec 2015 19:22:25 -0800 Subject: [PATCH 12/89] Add tests for PathFilterGroup.Single Expand the existing PathFilterGroup tests to check which paths the tree entry matches. This expands test coverage by ensuring that PathFilterGroup's simpler code path to match against a single PathFilter works correctly. While at it, move the check on tree entry d/e/f/g.y into two separate tests: one to check that it doesn't match any of the configured paths, and another to check that it does not throw StopWalkException to end the walk early. Change-Id: I55bd512cd049fc2018659e2f86a4b8650f171fda --- .../treewalk/filter/PathFilterGroupTest.java | 119 +++++++++++++----- 1 file changed, 86 insertions(+), 33 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java index d0062e199..296806723 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterGroupTest.java @@ -43,11 +43,18 @@ package org.eclipse.jgit.treewalk.filter; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEditor; @@ -58,6 +65,7 @@ import org.eclipse.jgit.errors.StopWalkException; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Sets; import org.eclipse.jgit.treewalk.TreeWalk; import org.junit.Before; import org.junit.Test; @@ -66,6 +74,8 @@ public class PathFilterGroupTest { private TreeFilter filter; + private Map singles; + @Before public void setup() { // @formatter:off @@ -81,64 +91,65 @@ public void setup() { }; // @formatter:on filter = PathFilterGroup.createFromStrings(paths); + singles = new HashMap<>(); + for (String path : paths) { + singles.put(path, PathFilterGroup.createFromStrings(path)); + } } @Test public void testExact() throws MissingObjectException, IncorrectObjectTypeException, IOException { - assertTrue(filter.include(fakeWalk("a"))); - assertTrue(filter.include(fakeWalk("b/c"))); - assertTrue(filter.include(fakeWalk("c/d/e"))); - assertTrue(filter.include(fakeWalk("c/d/f"))); - assertTrue(filter.include(fakeWalk("d/e/f/g"))); - assertTrue(filter.include(fakeWalk("d/e/f/g.x"))); + assertMatches(Sets.of("a"), fakeWalk("a")); + assertMatches(Sets.of("b/c"), fakeWalk("b/c")); + assertMatches(Sets.of("c/d/e"), fakeWalk("c/d/e")); + assertMatches(Sets.of("c/d/f"), fakeWalk("c/d/f")); + assertMatches(Sets.of("d/e/f/g"), fakeWalk("d/e/f/g")); + assertMatches(Sets.of("d/e/f/g.x"), fakeWalk("d/e/f/g.x")); } @Test public void testNoMatchButClose() throws MissingObjectException, IncorrectObjectTypeException, IOException { - assertFalse(filter.include(fakeWalk("a+"))); - assertFalse(filter.include(fakeWalk("b+/c"))); - assertFalse(filter.include(fakeWalk("c+/d/e"))); - assertFalse(filter.include(fakeWalk("c+/d/f"))); - assertFalse(filter.include(fakeWalk("c/d.a"))); - assertFalse(filter.include(fakeWalk("d+/e/f/g"))); + assertNoMatches(fakeWalk("a+")); + assertNoMatches(fakeWalk("b+/c")); + assertNoMatches(fakeWalk("c+/d/e")); + assertNoMatches(fakeWalk("c+/d/f")); + assertNoMatches(fakeWalk("c/d.a")); + assertNoMatches(fakeWalk("d+/e/f/g")); } @Test public void testJustCommonPrefixIsNotMatch() throws MissingObjectException, IncorrectObjectTypeException, IOException { - assertFalse(filter.include(fakeWalk("b/a"))); - assertFalse(filter.include(fakeWalk("b/d"))); - assertFalse(filter.include(fakeWalk("c/d/a"))); - assertFalse(filter.include(fakeWalk("d/e/e"))); + assertNoMatches(fakeWalk("b/a")); + assertNoMatches(fakeWalk("b/d")); + assertNoMatches(fakeWalk("c/d/a")); + assertNoMatches(fakeWalk("d/e/e")); + assertNoMatches(fakeWalk("d/e/f/g.y")); } @Test public void testKeyIsPrefixOfFilter() throws MissingObjectException, IncorrectObjectTypeException, IOException { - assertTrue(filter.include(fakeWalk("b"))); - assertTrue(filter.include(fakeWalk("c/d"))); - assertTrue(filter.include(fakeWalk("c/d"))); - assertTrue(filter.include(fakeWalk("c"))); - assertTrue(filter.include(fakeWalk("d/e/f"))); - assertTrue(filter.include(fakeWalk("d/e"))); - assertTrue(filter.include(fakeWalk("d"))); + assertMatches(Sets.of("b/c"), fakeWalk("b")); + assertMatches(Sets.of("c/d/e", "c/d/f"), fakeWalk("c/d")); + assertMatches(Sets.of("c/d/e", "c/d/f"), fakeWalk("c")); + assertMatches(Sets.of("d/e/f/g", "d/e/f/g.x"), fakeWalk("d/e/f")); + assertMatches(Sets.of("d/e/f/g", "d/e/f/g.x"), fakeWalk("d/e")); + assertMatches(Sets.of("d/e/f/g", "d/e/f/g.x"), fakeWalk("d")); } @Test public void testFilterIsPrefixOfKey() throws MissingObjectException, IncorrectObjectTypeException, IOException { - assertTrue(filter.include(fakeWalk("a/b"))); - assertTrue(filter.include(fakeWalk("b/c/d"))); - assertTrue(filter.include(fakeWalk("c/d/e/f"))); - assertTrue(filter.include(fakeWalk("c/d/f/g"))); - assertTrue(filter.include(fakeWalk("d/e/f/g/h"))); - assertTrue(filter.include(fakeWalk("d/e/f/g/y"))); - assertTrue(filter.include(fakeWalk("d/e/f/g.x/h"))); - // listed before g/y, so can't StopWalk here, but it's not included - // either - assertFalse(filter.include(fakeWalk("d/e/f/g.y"))); + assertMatches(Sets.of("a"), fakeWalk("a/b")); + assertMatches(Sets.of("b/c"), fakeWalk("b/c/d")); + assertMatches(Sets.of("c/d/e"), fakeWalk("c/d/e/f")); + assertMatches(Sets.of("c/d/f"), fakeWalk("c/d/f/g")); + assertMatches(Sets.of("d/e/f/g"), fakeWalk("d/e/f/g/h")); + assertMatches(Sets.of("d/e/f/g"), fakeWalk("d/e/f/g/y")); + assertMatches(Sets.of("d/e/f/g.x"), fakeWalk("d/e/f/g.x/h")); } @Test @@ -182,6 +193,10 @@ public void testStopWalk() throws MissingObjectException, // less obvious #2 due to git sorting order filter.include(fakeWalk("d/e/f/g/h.txt")); + // listed before g/y, so can't StopWalk here + filter.include(fakeWalk("d/e/f/g.y")); + singles.get("d/e/f/g").include(fakeWalk("d/e/f/g.y")); + // non-ascii try { filter.include(fakeWalk("\u00C0")); @@ -191,6 +206,44 @@ public void testStopWalk() throws MissingObjectException, } } + private void assertNoMatches(TreeWalk tw) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + assertMatches(Sets. of(), tw); + } + + private void assertMatches(Set expect, TreeWalk tw) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + List actual = new ArrayList<>(); + for (String path : singles.keySet()) { + if (includes(singles.get(path), tw)) { + actual.add(path); + } + } + + String[] e = expect.toArray(new String[expect.size()]); + String[] a = actual.toArray(new String[actual.size()]); + Arrays.sort(e); + Arrays.sort(a); + assertArrayEquals(e, a); + + if (expect.isEmpty()) { + assertFalse(includes(filter, tw)); + } else { + assertTrue(includes(filter, tw)); + } + } + + private static boolean includes(TreeFilter f, TreeWalk tw) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + try { + return f.include(tw); + } catch (StopWalkException e) { + return false; + } + } + TreeWalk fakeWalk(final String path) throws IOException { DirCache dc = DirCache.newInCore(); DirCacheEditor dce = dc.editor(); From 75a0dcac1883414f5f99eec37ffa9cb305940718 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Mon, 14 Dec 2015 19:57:24 -0800 Subject: [PATCH 13/89] Do not let PathFilter.create("a/b") match 'a' unless 'a' is a subtree PathFilter and PathFilterGroup form JGit's implementation of git's path-limiting feature in commands like log and diff. To save time when traversing trees, a path specification foo/bar/baz tells the tree walker not to traverse unrelated trees like qux/. It does that by returning false from include when the tree walker is visiting qux and true when it is visiting foo. Unfortunately that test was implemented to be slightly over-eager: it doesn't only return true when asked whether to visit a subtree "foo" but when asked about a plain file "foo" as well. As a result, diffs and logs restricted to some-file/non-existing-suffix unexpectedly match against some-file: $ jgit log -- LICENSE/no-such-file commit 629fd0d594d242eab26161b0dac34f7576fd4d3d Author: Shawn O. Pearce Date: Fri Jul 02 14:52:49 2010 -0700 Clean up LICENSE file [...] Fix it by checking against the entry's mode. Gitiles +log has the same bug and benefits from the same fix. Callers know not to worry about what subtrees are included in the tree walk because shouldBeRecursive() returns true in this case, so this behavior change should be safe. This also better matches the behavior of C git: $ empty=$(git mktree * This method tests that the supplied path is exactly equal to the current - * entry, or is one of its parent directories. It is faster to use this + * entry or is one of its parent directories. It is faster to use this * method then to use {@link #getPathString()} to first create a String * object, then test startsWith or some other type of string * match function. + *

+ * If the current entry is a subtree, then all paths within the subtree + * are considered to match it. * * @param p * path buffer to test. Callers should ensure the path does not @@ -900,7 +903,7 @@ public int isPathPrefix(final byte[] p, final int pLen) { // If p[ci] == '/' then pattern matches this subtree, // otherwise we cannot be certain so we return -1. // - return p[ci] == '/' ? 0 : -1; + return p[ci] == '/' && FileMode.TREE.equals(t.mode) ? 0 : -1; } // Both strings are identical. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java index bdfde0bfc..7601956c4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java @@ -245,9 +245,9 @@ public boolean include(final TreeWalk walker) { int hash = hasher.nextHash(); if (fullpaths.contains(rp, hasher.length(), hash)) return true; - if (!hasher.hasNext()) - if (prefixes.contains(rp, hasher.length(), hash)) - return true; + if (!hasher.hasNext() && walker.isSubtree() + && prefixes.contains(rp, hasher.length(), hash)) + return true; } final int cmp = walker.isPathPrefix(max, max.length); From f8eee3963a5639577004d51e6fce02024323b4ae Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Wed, 2 Dec 2015 13:11:20 +0100 Subject: [PATCH 14/89] Fix NPE in HttpSupport Bug: 483366 Change-Id: I107f1b44e0e6371e3cfbd1cc18a970412e1fc679 Signed-off-by: Matthias Sohn --- .../http/apache/HttpClientConnection.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java index d42d6f29e..de81bf82b 100644 --- a/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java +++ b/org.eclipse.jgit.http.apache/src/org/eclipse/jgit/transport/http/apache/HttpClientConnection.java @@ -100,7 +100,7 @@ public class HttpClientConnection implements HttpConnection { HttpClient client; - String urlStr; + URL url; HttpUriRequest req; @@ -176,16 +176,19 @@ public void setBuffer(TemporaryBuffer buffer) { /** * @param urlStr + * @throws MalformedURLException */ - public HttpClientConnection(String urlStr) { + public HttpClientConnection(String urlStr) throws MalformedURLException { this(urlStr, null); } /** * @param urlStr * @param proxy + * @throws MalformedURLException */ - public HttpClientConnection(String urlStr, Proxy proxy) { + public HttpClientConnection(String urlStr, Proxy proxy) + throws MalformedURLException { this(urlStr, proxy, null); } @@ -193,10 +196,12 @@ public HttpClientConnection(String urlStr, Proxy proxy) { * @param urlStr * @param proxy * @param cl + * @throws MalformedURLException */ - public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl) { + public HttpClientConnection(String urlStr, Proxy proxy, HttpClient cl) + throws MalformedURLException { this.client = cl; - this.urlStr = urlStr; + this.url = new URL(urlStr); this.proxy = proxy; } @@ -206,11 +211,7 @@ public int getResponseCode() throws IOException { } public URL getURL() { - try { - return new URL(urlStr); - } catch (MalformedURLException e) { - return null; - } + return url; } public String getResponseMessage() throws IOException { @@ -250,11 +251,11 @@ public void setRequestProperty(String name, String value) { public void setRequestMethod(String method) throws ProtocolException { this.method = method; if ("GET".equalsIgnoreCase(method)) //$NON-NLS-1$ - req = new HttpGet(urlStr); + req = new HttpGet(url.toString()); else if ("PUT".equalsIgnoreCase(method)) //$NON-NLS-1$ - req = new HttpPut(urlStr); + req = new HttpPut(url.toString()); else if ("POST".equalsIgnoreCase(method)) //$NON-NLS-1$ - req = new HttpPost(urlStr); + req = new HttpPost(url.toString()); else { this.method = null; throw new UnsupportedOperationException(); From 6340c76d8b50a48c3700f874bad3e6ece7349802 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Fri, 14 Aug 2015 14:03:57 +0200 Subject: [PATCH 15/89] Fix InterruptTimer leak in BasePackConnection When setting timeout on push, BasePackConnection creates a timer, which will be terminated when push finishes. But, when using SmartHttpPushConnection, it dropped the first timer created in the constructor and then created another timer in doPush. If new threads are created faster than the gc collects then this may stop the service if it's hitting the max process limit. Hence don't create a new timer if it already exists. Bug: 474947 Change-Id: I6746ffe4584ad919369afd5bdbba66fe736be314 Signed-off-by: Matthias Sohn --- .../src/org/eclipse/jgit/transport/BasePackConnection.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java index 7f9cec734..aa36aeb1b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java @@ -143,7 +143,9 @@ protected final void init(InputStream myIn, OutputStream myOut) { final int timeout = transport.getTimeout(); if (timeout > 0) { final Thread caller = Thread.currentThread(); - myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$ + if (myTimer == null) { + myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$ + } timeoutIn = new TimeoutInputStream(myIn, myTimer); timeoutOut = new TimeoutOutputStream(myOut, myTimer); timeoutIn.setTimeout(timeout * 1000); From 3096a133409ebda900415e9988be49c3426cbcdb Mon Sep 17 00:00:00 2001 From: James Kolb Date: Tue, 15 Dec 2015 17:14:45 -0500 Subject: [PATCH 16/89] Included cached deltas in delta packStatistics. Previously, non-reuse deltas were only included in packStatistics if they were not cached by the deltaWindow. Change-Id: I7684d8214875f0a7569b34614f8a3ba341dbde9c Signed-off-by: James Kolb --- .../internal/storage/file/PackWriterTest.java | 34 +++++++++++++++++++ .../internal/storage/pack/PackWriter.java | 2 ++ 2 files changed, 36 insertions(+) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java index bc880a13e..80efe199f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java @@ -48,6 +48,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -79,6 +80,7 @@ import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.pack.PackConfig; +import org.eclipse.jgit.storage.pack.PackStatistics; import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.eclipse.jgit.transport.PackParser; import org.junit.After; @@ -437,6 +439,38 @@ public void testWritePack4SizeThinVsNoThin() throws Exception { assertTrue(sizePack4 > sizePack4Thin); } + @Test + public void testDeltaStatistics() throws Exception { + config.setDeltaCompress(true); + FileRepository repo = createBareRepository(); + TestRepository testRepo = new TestRepository(repo); + ArrayList blobs = new ArrayList<>(); + blobs.add(testRepo.blob(genDeltableData(1000))); + blobs.add(testRepo.blob(genDeltableData(1005))); + + try (PackWriter pw = new PackWriter(repo)) { + NullProgressMonitor m = NullProgressMonitor.INSTANCE; + pw.preparePack(blobs.iterator()); + pw.writePack(m, m, os); + PackStatistics stats = pw.getStatistics(); + assertEquals(1, stats.getTotalDeltas()); + assertTrue("Delta bytes not set.", + stats.byObjectType(OBJ_BLOB).getDeltaBytes() > 0); + } + } + + // Generate consistent junk data for building files that delta well + private String genDeltableData(int length) { + assertTrue("Generated data must have a length > 0", length > 0); + char[] data = {'a', 'b', 'c', '\n'}; + StringBuilder builder = new StringBuilder(length); + for (int i = 0; i < length; i++) { + builder.append(data[i % 4]); + } + return builder.toString(); + } + + @Test public void testWriteIndex() throws Exception { config.setIndexVersion(2); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java index 19b6b080d..f3f77c449 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java @@ -1551,6 +1551,8 @@ private void writeDeltaObjectDeflate(PackOutputStream out, if (zbuf != null) { out.writeHeader(otp, otp.getCachedSize()); out.write(zbuf); + typeStats.cntDeltas++; + typeStats.deltaBytes += out.length() - otp.getOffset(); return; } } From a38531b21c7e2b0dc956e0ed1bfc9513f604273c Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 4 Dec 2015 14:56:14 -0800 Subject: [PATCH 17/89] ProgressSpinner: Simple busy wait entertainment Keep a user amused while the server does work by spinning a little ASCII-art object on a single line. Change-Id: Ie8f181d1aa606d4ae69e5d3ca4db387cea739f38 --- .../jgit/transport/ProgressSpinner.java | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java new file mode 100644 index 000000000..ac048a14a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2015, Google Inc. + * 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.transport; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.TimeUnit; + +/** + * A simple spinner connected to an {@code OutputStream}. + *

+ * This is class is not thread-safe. The update method may only be used from a + * single thread. Updates are sent only as frequently as {@link #update()} is + * invoked by the caller, and are capped at no more than 2 times per second by + * requiring at least 500 milliseconds between updates. + * + * @since 4.2 + */ +public class ProgressSpinner { + private static final long MIN_REFRESH_MILLIS = 500; + private static final char[] STATES = new char[] { '-', '\\', '|', '/' }; + + private final OutputStream out; + private String msg; + private int state; + private boolean write; + private boolean shown; + private long nextUpdateMillis; + + /** + * Initialize a new spinner. + * + * @param out + * where to send output to. + */ + public ProgressSpinner(OutputStream out) { + this.out = out; + this.write = true; + } + + /** + * Begin a time consuming task. + * + * @param title + * description of the task, suitable for human viewing. + * @param delay + * delay to wait before displaying anything at all. + * @param delayUnits + * unit for {@code delay}. + */ + public void beginTask(String title, long delay, TimeUnit delayUnits) { + msg = title; + state = 0; + shown = false; + + long now = System.currentTimeMillis(); + if (delay > 0) { + nextUpdateMillis = now + delayUnits.toMillis(delay); + } else { + send(now); + } + } + + /** Update the spinner if it is showing. */ + public void update() { + long now = System.currentTimeMillis(); + if (now >= nextUpdateMillis) { + send(now); + state = (state + 1) % STATES.length; + } + } + + private void send(long now) { + StringBuilder buf = new StringBuilder(msg.length() + 16); + buf.append('\r').append(msg).append("... ("); //$NON-NLS-1$ + buf.append(STATES[state]); + buf.append(") "); //$NON-NLS-1$ + shown = true; + write(buf.toString()); + nextUpdateMillis = now + MIN_REFRESH_MILLIS; + } + + /** + * Denote the current task completed. + * + * @param result + * text to print after the task's title + * {@code "$title ... $result"}. + */ + public void endTask(String result) { + if (shown) { + write('\r' + msg + "... " + result + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + private void write(String s) { + if (write) { + try { + out.write(s.getBytes(UTF_8)); + out.flush(); + } catch (IOException e) { + write = false; + } + } + } +} From 1aafa619589106434db1f239e89b2334432fdc18 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Wed, 16 Dec 2015 16:41:34 +0100 Subject: [PATCH 18/89] Checkout should be able to override modified symbolic links Handle existing symlink as a file, not as directory if deleting a file before creating (overriding) a symlink. Bug: 484491 Change-Id: I29dbf57d1daec2ba98454975b093e1d381d05196 Signed-off-by: Andrey Loskutov --- .../jgit/api/PathCheckoutCommandTest.java | 65 +++++++++++++++++++ .../org/eclipse/jgit/util/FileUtilTest.java | 26 +++++--- .../src/org/eclipse/jgit/util/FileUtils.java | 4 +- 3 files changed, 86 insertions(+), 9 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java index db811cdf5..3343af06d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PathCheckoutCommandTest.java @@ -43,10 +43,12 @@ package org.eclipse.jgit.api; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; +import java.nio.file.Path; import org.eclipse.jgit.api.CheckoutCommand.Stage; import org.eclipse.jgit.api.errors.JGitInternalException; @@ -59,6 +61,9 @@ import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FileUtils; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -73,6 +78,8 @@ public class PathCheckoutCommandTest extends RepositoryTestCase { private static final String FILE3 = "Test3.txt"; + private static final String LINK = "link"; + Git git; RevCommit initialCommit; @@ -98,6 +105,64 @@ public void setUp() throws Exception { git.commit().setMessage("Third commit").call(); } + @Test + public void testUpdateSymLink() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + + Path path = writeLink(LINK, FILE1); + git.add().addFilepattern(LINK).call(); + git.commit().setMessage("Added link").call(); + assertEquals("3", read(path.toFile())); + + writeLink(LINK, FILE2); + assertEquals("c", read(path.toFile())); + + CheckoutCommand co = git.checkout(); + co.addPath(LINK).call(); + + assertEquals("3", read(path.toFile())); + } + + @Test + public void testUpdateBrokenSymLinkToDirectory() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + + Path path = writeLink(LINK, "f"); + git.add().addFilepattern(LINK).call(); + git.commit().setMessage("Added link").call(); + assertEquals("f", FileUtils.readSymLink(path.toFile())); + assertTrue(path.toFile().exists()); + + writeLink(LINK, "link_to_nowhere"); + assertFalse(path.toFile().exists()); + assertEquals("link_to_nowhere", FileUtils.readSymLink(path.toFile())); + + CheckoutCommand co = git.checkout(); + co.addPath(LINK).call(); + + assertEquals("f", FileUtils.readSymLink(path.toFile())); + } + + @Test + public void testUpdateBrokenSymLink() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + + Path path = writeLink(LINK, FILE1); + git.add().addFilepattern(LINK).call(); + git.commit().setMessage("Added link").call(); + assertEquals("3", read(path.toFile())); + assertEquals(FILE1, FileUtils.readSymLink(path.toFile())); + + writeLink(LINK, "link_to_nowhere"); + assertFalse(path.toFile().exists()); + assertEquals("link_to_nowhere", FileUtils.readSymLink(path.toFile())); + + CheckoutCommand co = git.checkout(); + co.addPath(LINK).call(); + + assertEquals("3", read(path.toFile())); + } + @Test public void testUpdateWorkingDirectory() throws Exception { CheckoutCommand co = git.checkout(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java index 0d7d31b3a..1f78e0208 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtilTest.java @@ -54,6 +54,7 @@ import org.eclipse.jgit.junit.JGitTestUtil; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -424,18 +425,27 @@ public void testRenameOverExistingEmptyDirectory() throws IOException { @Test public void testCreateSymlink() throws IOException { FS fs = FS.DETECTED; - try { - fs.createSymLink(new File(trash, "x"), "y"); - } catch (IOException e) { - if (fs.supportsSymlinks()) - fail("FS claims to support symlinks but attempt to create symlink failed"); - return; - } - assertTrue(fs.supportsSymlinks()); + // show test as ignored if the FS doesn't support symlinks + Assume.assumeTrue(fs.supportsSymlinks()); + fs.createSymLink(new File(trash, "x"), "y"); String target = fs.readSymLink(new File(trash, "x")); assertEquals("y", target); } + @Test + public void testCreateSymlinkOverrideExisting() throws IOException { + FS fs = FS.DETECTED; + // show test as ignored if the FS doesn't support symlinks + Assume.assumeTrue(fs.supportsSymlinks()); + File file = new File(trash, "x"); + fs.createSymLink(file, "y"); + String target = fs.readSymLink(file); + assertEquals("y", target); + fs.createSymLink(file, "z"); + target = fs.readSymLink(file); + assertEquals("z", target); + } + @Test public void testRelativize_doc() { // This is the javadoc example diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java index 727ea79cc..aa101f73f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -409,7 +409,9 @@ public static Path createSymLink(File path, String target) throws IOException { Path nioPath = path.toPath(); if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) { - if (Files.isRegularFile(nioPath)) { + BasicFileAttributes attrs = Files.readAttributes(nioPath, + BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + if (attrs.isRegularFile() || attrs.isSymbolicLink()) { delete(path); } else { delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE); From da5ac45c25d02c7619e870ab333aa7aa8de4520a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Herrmann?= Date: Wed, 16 Dec 2015 13:18:52 +0100 Subject: [PATCH 19/89] Remove unused import 'org.apache.commons.codec.binary' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I7db35f4360e29d006d1e4e6ccfaa78ae598e3b4e Signed-off-by: Rüdiger Herrmann --- org.eclipse.jgit.pgm/META-INF/MANIFEST.MF | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index bd09e4e74..9cbd377fe 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -7,8 +7,7 @@ Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy Bundle-Localization: plugin Bundle-RequiredExecutionEnvironment: JavaSE-1.7 -Import-Package: org.apache.commons.codec.binary;version="[1.6.0,2.0.0)", - org.apache.commons.compress.archivers;version="[1.3,2.0)", +Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)", org.apache.commons.compress.archivers.tar;version="[1.3,2.0)", org.apache.commons.compress.archivers.zip;version="[1.3,2.0)", org.eclipse.jgit.api;version="[4.2.0,4.3.0)", From d88695e41277fa36dd4ba5163e232cf0c39cbb51 Mon Sep 17 00:00:00 2001 From: Yuxuan 'fishy' Wang Date: Thu, 17 Dec 2015 18:27:56 -0800 Subject: [PATCH 20/89] Skip nested copyfiles in RepoCommand. Similar to nested directories, nested copyfiles won't work with git submodule either. Change-Id: Idbe965ec20a682fca0432802858162f8238f05de Signed-off-by: Yuxuan 'fishy' Wang --- .../eclipse/jgit/gitrepo/RepoCommandTest.java | 7 +++- .../eclipse/jgit/gitrepo/ManifestParser.java | 32 +++++++++++++++++++ .../org/eclipse/jgit/gitrepo/RepoProject.java | 23 ++++++++++++- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java index b6649b3f0..524d0b8e7 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java @@ -409,6 +409,7 @@ public void testCopyFileBare() throws Exception { .append("") .append("") + .append("") .append("").append(""); JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", xmlContent.toString()); @@ -423,8 +424,12 @@ public void testCopyFileBare() throws Exception { .getRepository(); // The Hello file should exist File hello = new File(localDb.getWorkTree(), "Hello"); - localDb.close(); assertTrue("The Hello file should exist", hello.exists()); + // The foo/Hello file should be skipped. + File foohello = new File(localDb.getWorkTree(), "foo/Hello"); + assertFalse( + "The foo/Hello file should be skipped", foohello.exists()); + localDb.close(); // The content of Hello file should be expected BufferedReader reader = new BufferedReader(new FileReader(hello)); String content = reader.readLine(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java index 891479d1f..7eb955006 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java @@ -338,6 +338,20 @@ void removeOverlaps() { else last = p; } + removeNestedCopyfiles(); + } + + /** Remove copyfiles that sit in a subdirectory of any other project. */ + void removeNestedCopyfiles() { + for (RepoProject proj : filteredProjects) { + List copyfiles = new ArrayList<>(proj.getCopyFiles()); + proj.clearCopyFiles(); + for (CopyFile copyfile : copyfiles) { + if (!isNestedCopyfile(copyfile)) { + proj.addCopyFile(copyfile); + } + } + } } boolean inGroups(RepoProject proj) { @@ -357,4 +371,22 @@ boolean inGroups(RepoProject proj) { } return false; } + + private boolean isNestedCopyfile(CopyFile copyfile) { + if (copyfile.dest.indexOf('/') == -1) { + // If the copyfile is at root level then it won't be nested. + return false; + } + for (RepoProject proj : filteredProjects) { + if (proj.getPath().compareTo(copyfile.dest) > 0) { + // Early return as remaining projects can't be ancestor of this + // copyfile config (filteredProjects is sorted). + return false; + } + if (proj.isAncestorOf(copyfile.dest)) { + return true; + } + } + return false; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java index 9a072114a..915066d58 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java @@ -258,6 +258,15 @@ public void addCopyFiles(Collection copyfiles) { this.copyfiles.addAll(copyfiles); } + /** + * Clear all the copyfiles. + * + * @since 4.2 + */ + public void clearCopyFiles() { + this.copyfiles.clear(); + } + private String getPathWithSlash() { if (path.endsWith("/")) //$NON-NLS-1$ return path; @@ -273,7 +282,19 @@ private String getPathWithSlash() { * @return true if this sub repo is the ancestor of given sub repo. */ public boolean isAncestorOf(RepoProject that) { - return that.getPathWithSlash().startsWith(this.getPathWithSlash()); + return isAncestorOf(that.getPathWithSlash()); + } + + /** + * Check if this sub repo is an ancestor of the given path. + * + * @param path + * path to be checked to see if it is within this repository + * @return true if this sub repo is an ancestor of the given path. + * @since 4.2 + */ + public boolean isAncestorOf(String path) { + return path.startsWith(getPathWithSlash()); } @Override From 9c71bf14b70240907ee880a18ba9977cb4e7bbdb Mon Sep 17 00:00:00 2001 From: Dmitry Neverov Date: Mon, 21 Dec 2015 20:15:42 +0100 Subject: [PATCH 21/89] Close copy threads in case of errors Bug: 484775 Change-Id: I3c7105188e615b6b994261f4ece0c8abc98eb444 Signed-off-by: Dmitry Neverov --- .../org/eclipse/jgit/transport/JschSession.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java index 85109a5bf..1dfe5d979 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java @@ -149,14 +149,27 @@ private class JschProcess extends Process { channel.setCommand(commandName); setupStreams(); channel.connect(timeout > 0 ? timeout * 1000 : 0); - if (!channel.isConnected()) + if (!channel.isConnected()) { + closeOutputStream(); throw new TransportException(uri, JGitText.get().connectionFailed); + } } catch (JSchException e) { + closeOutputStream(); throw new TransportException(uri, e.getMessage(), e); } } + private void closeOutputStream() { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException ioe) { + // ignore + } + } + } + private void setupStreams() throws IOException { inputStream = channel.getInputStream(); From bb8627bd9bed4d42e3763df9ba86f45b1fc20892 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 23 Dec 2015 20:15:00 -0800 Subject: [PATCH 22/89] AddCommand: Cleanup conditional logic Unnest and simplify conditional logic for handling entries. Change-Id: I3093cab5f0edfaf3efbbd6c644e9c922edc67d38 --- .../src/org/eclipse/jgit/api/AddCommand.java | 99 ++++++++++--------- 1 file changed, 52 insertions(+), 47 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java index 67fb342fe..40102447a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java @@ -43,6 +43,9 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.FileMode.GITLINK; + import java.io.IOException; import java.io.InputStream; import java.util.Collection; @@ -58,8 +61,8 @@ import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.FileTreeIterator; @@ -135,15 +138,12 @@ public DirCache call() throws GitAPIException, NoFilepatternException { throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired); checkCallable(); DirCache dc = null; - boolean addAll = false; - if (filepatterns.contains(".")) //$NON-NLS-1$ - addAll = true; + boolean addAll = filepatterns.contains("."); //$NON-NLS-1$ try (ObjectInserter inserter = repo.newObjectInserter(); final TreeWalk tw = new TreeWalk(repo)) { tw.setOperationType(OperationType.CHECKIN_OP); dc = repo.lockDirCache(); - DirCacheIterator c; DirCacheBuilder builder = dc.builder(); tw.addTree(new DirCacheBuildIterator(builder)); @@ -158,55 +158,60 @@ public DirCache call() throws GitAPIException, NoFilepatternException { String lastAddedFile = null; while (tw.next()) { - String path = tw.getPathString(); - + DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); WorkingTreeIterator f = tw.getTree(1, WorkingTreeIterator.class); - if (tw.getTree(0, DirCacheIterator.class) == null && - f != null && f.isEntryIgnored()) { + if (c == null && f != null && f.isEntryIgnored()) { // file is not in index but is ignored, do nothing + continue; + } else if (c == null && update) { + // Only update of existing entries was requested. + continue; } - // In case of an existing merge conflict the - // DirCacheBuildIterator iterates over all stages of - // this path, we however want to add only one - // new DirCacheEntry per path. - else if (!(path.equals(lastAddedFile))) { - if (!(update && tw.getTree(0, DirCacheIterator.class) == null)) { - c = tw.getTree(0, DirCacheIterator.class); - if (f != null) { // the file exists - long sz = f.getEntryLength(); - DirCacheEntry entry = new DirCacheEntry(path); - if (c == null || c.getDirCacheEntry() == null - || !c.getDirCacheEntry().isAssumeValid()) { - FileMode mode = f.getIndexFileMode(c); - entry.setFileMode(mode); - if (FileMode.GITLINK != mode) { - entry.setLength(sz); - entry.setLastModified(f - .getEntryLastModified()); - long contentSize = f - .getEntryContentLength(); - InputStream in = f.openEntryStream(); - try { - entry.setObjectId(inserter.insert( - Constants.OBJ_BLOB, contentSize, in)); - } finally { - in.close(); - } - } else - entry.setObjectId(f.getEntryObjectId()); - builder.add(entry); - lastAddedFile = path; - } else { - builder.add(c.getDirCacheEntry()); - } + String path = tw.getPathString(); + if (path.equals(lastAddedFile)) { + // In case of an existing merge conflict the + // DirCacheBuildIterator iterates over all stages of + // this path, we however want to add only one + // new DirCacheEntry per path. + continue; + } - } else if (c != null - && (!update || FileMode.GITLINK == c - .getEntryFileMode())) - builder.add(c.getDirCacheEntry()); + if (f == null) { // working tree file does not exist + if (c != null + && (!update || GITLINK == c.getEntryFileMode())) { + builder.add(c.getDirCacheEntry()); } + continue; } + + if (c != null && c.getDirCacheEntry() != null + && c.getDirCacheEntry().isAssumeValid()) { + // Index entry is marked assume valid. Even though + // the user specified the file to be added JGit does + // not consider the file for addition. + builder.add(c.getDirCacheEntry()); + continue; + } + + long sz = f.getEntryLength(); + DirCacheEntry entry = new DirCacheEntry(path); + FileMode mode = f.getIndexFileMode(c); + entry.setFileMode(mode); + + if (GITLINK != mode) { + entry.setLength(sz); + entry.setLastModified(f.getEntryLastModified()); + long len = f.getEntryContentLength(); + try (InputStream in = f.openEntryStream()) { + ObjectId id = inserter.insert(OBJ_BLOB, len, in); + entry.setObjectId(id); + } + } else { + entry.setObjectId(f.getEntryObjectId()); + } + builder.add(entry); + lastAddedFile = path; } inserter.flush(); builder.commit(); From a94e517940b6c7a5173bba5cb8b3312755751b7d Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 23 Dec 2015 22:06:05 -0800 Subject: [PATCH 23/89] AddCommand: Avoid unnecessary string conversions Change-Id: I13634caeccd9f675a86adfdfa94099b6fb75463a --- .../src/org/eclipse/jgit/api/AddCommand.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java index 40102447a..8a2c08c80 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java @@ -155,7 +155,7 @@ public DirCache call() throws GitAPIException, NoFilepatternException { if (!addAll) tw.setFilter(PathFilterGroup.createFromStrings(filepatterns)); - String lastAddedFile = null; + byte[] lastAdded = null; while (tw.next()) { DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); @@ -168,8 +168,11 @@ public DirCache call() throws GitAPIException, NoFilepatternException { continue; } - String path = tw.getPathString(); - if (path.equals(lastAddedFile)) { + DirCacheEntry entry = c != null ? c.getDirCacheEntry() : null; + if (entry != null && entry.getStage() > 0 + && lastAdded != null + && lastAdded.length == tw.getPathLength() + && tw.isPathPrefix(lastAdded, lastAdded.length) == 0) { // In case of an existing merge conflict the // DirCacheBuildIterator iterates over all stages of // this path, we however want to add only one @@ -180,27 +183,28 @@ public DirCache call() throws GitAPIException, NoFilepatternException { if (f == null) { // working tree file does not exist if (c != null && (!update || GITLINK == c.getEntryFileMode())) { - builder.add(c.getDirCacheEntry()); + builder.add(entry); } continue; } - if (c != null && c.getDirCacheEntry() != null - && c.getDirCacheEntry().isAssumeValid()) { + if (entry != null && entry.isAssumeValid()) { // Index entry is marked assume valid. Even though // the user specified the file to be added JGit does // not consider the file for addition. - builder.add(c.getDirCacheEntry()); + builder.add(entry); continue; } - long sz = f.getEntryLength(); - DirCacheEntry entry = new DirCacheEntry(path); + byte[] path = tw.getRawPath(); + if (entry == null || entry.getStage() > 0) { + entry = new DirCacheEntry(path); + } FileMode mode = f.getIndexFileMode(c); entry.setFileMode(mode); if (GITLINK != mode) { - entry.setLength(sz); + entry.setLength(f.getEntryLength()); entry.setLastModified(f.getEntryLastModified()); long len = f.getEntryContentLength(); try (InputStream in = f.openEntryStream()) { @@ -208,10 +212,12 @@ public DirCache call() throws GitAPIException, NoFilepatternException { entry.setObjectId(id); } } else { + entry.setLength(0); + entry.setLastModified(0); entry.setObjectId(f.getEntryObjectId()); } builder.add(entry); - lastAddedFile = path; + lastAdded = path; } inserter.flush(); builder.commit(); From c7c2897527659262cd4b6de0595eb2f369d8f724 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Thu, 17 Dec 2015 00:12:04 +0100 Subject: [PATCH 24/89] Allow checkout paths without specifying branch name JGit CLI should allow to do this: checkout -- Currently, even if "a" is a valid path in the git repo, jgit CLI can't checkout it: $jgit checkout -- a error: pathspec 'a' did not match any file(s) known to git. The fix also fixes at same time "unnamed" zombie "[VAL ...]" argument shown on the command line. Before fix: $jgit -h jgit checkout name [VAL ...] [-- path ... ...] [--force (-f)] [--help (-h)] [--orphan] [-b] After fix: $jgit -h jgit checkout [name] [-- path ... ...] [--force (-f)] [--help (-h)] [--orphan] [-b] Bug: 475765 Change-Id: I2b0e77959a72e4aac68452dc3846adaa745b0831 Signed-off-by: Andrey Loskutov --- .../jgit/lib/CLIRepositoryTestCase.java | 13 +++++++ .../org/eclipse/jgit/pgm/CheckoutTest.java | 37 +++++++++++++++++++ .../src/org/eclipse/jgit/pgm/Checkout.java | 7 ++-- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java index 559a6d5d4..4bdfa4d5b 100644 --- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java +++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java @@ -46,6 +46,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -76,6 +77,18 @@ protected String[] execute(String... cmds) throws Exception { return result.toArray(new String[0]); } + /** + * @param link + * the path of the symbolic link to create + * @param target + * the target of the symbolic link + * @return the path to the symbolic link + * @throws Exception + */ + protected Path writeLink(String link, String target) throws Exception { + return JGitTestUtil.writeLink(db, link, target); + } + protected File writeTrashFile(final String name, final String data) throws IOException { return JGitTestUtil.writeTrashFile(db, name, data); diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java index 939a9f6fd..167d8ab51 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java @@ -44,9 +44,14 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; import java.util.List; import org.eclipse.jgit.api.Git; @@ -59,7 +64,9 @@ import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator.FileEntry; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; +import org.junit.Assume; import org.junit.Test; public class CheckoutTest extends CLIRepositoryTestCase { @@ -578,4 +585,34 @@ public void testCheckoutPath() throws Exception { execute("git branch")); assertEquals("Hello world b", read(b)); } + + @Test + public void testCheckouSingleFile() throws Exception { + try (Git git = new Git(db)) { + File a = writeTrashFile("a", "file a"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("commit file a").call(); + writeTrashFile("a", "b"); + assertEquals("b", read(a)); + assertEquals("[]", Arrays.toString(execute("git checkout -- a"))); + assertEquals("file a", read(a)); + } + } + + @Test + public void testCheckoutLink() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); + try (Git git = new Git(db)) { + Path path = writeLink("a", "link_a"); + assertTrue(Files.isSymbolicLink(path)); + git.add().addFilepattern(".").call(); + git.commit().setMessage("commit link a").call(); + deleteTrashFile("a"); + writeTrashFile("a", "Hello world a"); + assertFalse(Files.isSymbolicLink(path)); + assertEquals("[]", Arrays.toString(execute("git checkout -- a"))); + assertEquals("link_a", FileUtils.readSymLink(path.toFile())); + assertTrue(Files.isSymbolicLink(path)); + } + } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java index 45794629e..94517dbf2 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java @@ -60,7 +60,7 @@ import org.eclipse.jgit.pgm.internal.CLIText; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; -import org.kohsuke.args4j.spi.StopOptionHandler; +import org.kohsuke.args4j.spi.RestOfArgumentsHandler; @Command(common = true, usage = "usage_checkout") class Checkout extends TextBuiltin { @@ -74,11 +74,10 @@ class Checkout extends TextBuiltin { @Option(name = "--orphan", usage = "usage_orphan") private boolean orphan = false; - @Argument(required = true, index = 0, metaVar = "metaVar_name", usage = "usage_checkout") + @Argument(required = false, index = 0, metaVar = "metaVar_name", usage = "usage_checkout") private String name; - @Argument(index = 1) - @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = StopOptionHandler.class) + @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class) private List paths = new ArrayList(); @Override From 110d3ca5952978e1b95ad05cdd3307092ee0c8b8 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Thu, 24 Dec 2015 15:38:02 -0800 Subject: [PATCH 25/89] DirCacheEditor: Fix formatting to avoid , at start of line Change-Id: I1b1d614470c67fe4736fdc9c26ae26fb38dd58b5 --- .../src/org/eclipse/jgit/dircache/DirCacheEditor.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java index 13885d370..aa2616044 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java @@ -149,9 +149,11 @@ private void applyEdits() { if (missing) { final DirCacheEntry ent = new DirCacheEntry(e.path); e.apply(ent); - if (ent.getRawMode() == 0) - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().fileModeNotSetForPath - , ent.getPathString())); + if (ent.getRawMode() == 0) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().fileModeNotSetForPath, + ent.getPathString())); + } fastAdd(ent); } else { // Apply to all entries of the current path (different stages) From ef757a7e126e059bd0afe64949440813df791de3 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Thu, 24 Dec 2015 15:46:19 -0800 Subject: [PATCH 26/89] DirCacheEditor: Cleanup DeleteTree constructor Neaten up formatting and avoid strings, which prevents the need for NLS comment tags. Instead check the last character using char literal, and append a char literal instead of a string. Change-Id: Ib68e017769a1f5c03200354a805769d585a48c8b --- .../src/org/eclipse/jgit/dircache/DirCacheEditor.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java index aa2616044..932ef94db 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java @@ -274,10 +274,11 @@ public static final class DeleteTree extends PathEdit { * only the subtree's contents are matched by the command. * The special case "" (not "/"!) deletes all entries. */ - public DeleteTree(final String entryPath) { - super( - (entryPath.endsWith("/") || entryPath.length() == 0) ? entryPath //$NON-NLS-1$ - : entryPath + "/"); //$NON-NLS-1$ + public DeleteTree(String entryPath) { + super(entryPath.isEmpty() + || entryPath.charAt(entryPath.length() - 1) == '/' + ? entryPath + : entryPath + '/'); } public void apply(final DirCacheEntry ent) { From 241b50be319e29cfae2ab1a5fcec94e7931074f8 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Mon, 28 Dec 2015 15:42:04 +0100 Subject: [PATCH 27/89] Simplify development of commands: added main() to CLIGitCommand This will execute git commands (with arguments) specified on the command line, handy for developing/debugging a sequence of arbitrary git commands working on same repository. The git working dir path can be specified via Java system property "git_work_tree". If not specified, current directory will be used. Change-Id: I621a9ec198c31e28a383818efeb4b3f835ba1d6f Signed-off-by: Andrey Loskutov --- .../META-INF/MANIFEST.MF | 1 + .../jgit/lib/CLIRepositoryTestCase.java | 12 ++++++- .../org/eclipse/jgit/pgm/CLIGitCommand.java | 31 +++++++++++++++++++ org.eclipse.jgit/META-INF/MANIFEST.MF | 2 +- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF index db23c3b55..2514fdff7 100644 --- a/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm.test/META-INF/MANIFEST.MF @@ -11,6 +11,7 @@ Import-Package: org.eclipse.jgit.api;version="[4.2.0,4.3.0)", org.eclipse.jgit.api.errors;version="[4.2.0,4.3.0)", org.eclipse.jgit.diff;version="[4.2.0,4.3.0)", org.eclipse.jgit.dircache;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.file;version="4.2.0", org.eclipse.jgit.junit;version="[4.2.0,4.3.0)", org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", org.eclipse.jgit.merge;version="[4.2.0,4.3.0)", diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java index 4bdfa4d5b..4bf9b43cb 100644 --- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java +++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java @@ -70,10 +70,20 @@ public void setUp() throws Exception { trash = db.getWorkTree(); } + /** + * Executes specified git commands (with arguments) + * + * @param cmds + * each string argument must be a valid git command line, e.g. + * "git branch -h" + * @return command output + * @throws Exception + */ protected String[] execute(String... cmds) throws Exception { List result = new ArrayList(cmds.length); - for (String cmd : cmds) + for (String cmd : cmds) { result.addAll(CLIGitCommand.execute(cmd, db)); + } return result.toArray(new String[0]); } diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java index d77b1505a..bf15fed81 100644 --- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java +++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java @@ -43,10 +43,12 @@ package org.eclipse.jgit.pgm; import java.io.ByteArrayOutputStream; +import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; +import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.pgm.opt.CmdLineParser; @@ -69,6 +71,35 @@ public List getArguments() { return arguments; } + /** + * Executes git commands (with arguments) specified on the command line. The + * git repository (same for all commands) can be specified via system + * property "-Dgit_work_tree=path_to_work_tree". If the property is not set, + * current directory is used. + * + * @param args + * each element in the array must be a valid git command line, + * e.g. "git branch -h" + * @throws Exception + */ + public static void main(String[] args) throws Exception { + String workDir = System.getProperty("git_work_tree"); + if (workDir == null) { + workDir = "."; + System.out.println( + "System property 'git_work_tree' not specified, using current directory: " + + new File(workDir).getAbsolutePath()); + } + try (Repository db = new FileRepository(workDir + "/.git")) { + for (String cmd : args) { + List result = execute(cmd, db); + for (String line : result) { + System.out.println(line); + } + } + } + } + public static List execute(String str, Repository db) throws Exception { try { diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 2a953b559..66529d3af 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -65,7 +65,7 @@ Export-Package: org.eclipse.jgit.annotations;version="4.2.0", org.eclipse.jgit.junit, org.eclipse.jgit.junit.http, org.eclipse.jgit.http.server, - org.eclipse.jgit.java7.test, + org.eclipse.jgit.pgm.test, org.eclipse.jgit.pgm", org.eclipse.jgit.internal.storage.pack;version="4.2.0";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", org.eclipse.jgit.lib;version="4.2.0"; From 4b7839cafd3561bbeca6ed6dabce3d9039ab8288 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Mon, 28 Dec 2015 18:13:35 +0100 Subject: [PATCH 28/89] Provide a root cause for aborted commands Change-Id: Iafaa03dbacbe7f1b2b074d3294db988b08fdb0d7 Signed-off-by: Andrey Loskutov --- .../src/org/eclipse/jgit/pgm/Die.java | 15 +++++++++++++++ .../src/org/eclipse/jgit/pgm/TextBuiltin.java | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java index f07df1a4b..a25f1e930 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java @@ -86,6 +86,21 @@ public Die(final String why, final Throwable cause) { * @since 3.4 */ public Die(boolean aborted) { + this(aborted, null); + } + + /** + * Construct a new exception reflecting the fact that the command execution + * has been aborted before running. + * + * @param aborted + * boolean indicating the fact the execution has been aborted + * @param cause + * can be null + * @since 4.2 + */ + public Die(boolean aborted, final Throwable cause) { + super(cause != null ? cause.getMessage() : null, cause); this.aborted = aborted; } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java index 56cfc7e8e..40a42e520 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java @@ -217,7 +217,7 @@ protected void parseArguments(final String[] args) throws IOException { } catch (CmdLineException err) { if (!help) { this.errw.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage())); - throw die(true); + throw die(true, err); } } @@ -324,6 +324,19 @@ protected static Die die(boolean aborted) { return new Die(aborted); } + /** + * @param aborted + * boolean indicating that the execution has been aborted before + * running + * @param cause + * why the command has failed. + * @return a runtime exception the caller is expected to throw + * @since 4.2 + */ + protected static Die die(boolean aborted, final Throwable cause) { + return new Die(aborted, cause); + } + String abbreviateRef(String dst, boolean abbreviateRemote) { if (dst.startsWith(R_HEADS)) dst = dst.substring(R_HEADS.length()); From c59d86c0a79f913a8d6a62f2aa37dddcb1e2142c Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Mon, 28 Dec 2015 23:27:09 +0100 Subject: [PATCH 29/89] Don't treat command termination due '-h' option as a fatal error Signal early command termination due '-h' or '--help' option via TerminatedByHelpException. This allows tests using CLIGitCommand differentiate between unexpected command parsing errors and expected command cancellation "on help" (which also allows validation of expected/unexpected help messages). Additional side-effect: jgit supports now git style of handling help option: any unexpected command line options before help are reported as errors, but after help ignored. Bug: 484951 Change-Id: If45c41c0d32895ab6822a7ff9d851877dcef5771 Signed-off-by: Andrey Loskutov --- .../org/eclipse/jgit/pgm/CLIGitCommand.java | 31 +++++- .../org/eclipse/jgit/pgm/DescribeTest.java | 22 +++++ .../src/org/eclipse/jgit/pgm/Main.java | 17 +++- .../src/org/eclipse/jgit/pgm/Remote.java | 3 +- .../src/org/eclipse/jgit/pgm/TextBuiltin.java | 58 ++++++++++- .../eclipse/jgit/pgm/opt/CmdLineParser.java | 97 +++++++++++++++++-- 6 files changed, 208 insertions(+), 20 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java index bf15fed81..7d2cbca72 100644 --- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java +++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java @@ -50,6 +50,7 @@ import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.pgm.TextBuiltin.TerminatedByHelpException; import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.pgm.opt.CmdLineParser; import org.eclipse.jgit.pgm.opt.SubcommandHandler; @@ -120,12 +121,15 @@ public static byte[] rawExecute(String str, Repository db) System.arraycopy(args, 1, argv, 0, args.length - 1); CLIGitCommand bean = new CLIGitCommand(); - final CmdLineParser clp = new CmdLineParser(bean); + final CmdLineParser clp = new TestCmdLineParser(bean); clp.parseArgument(argv); final TextBuiltin cmd = bean.getSubcommand(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); cmd.outs = baos; + ByteArrayOutputStream errs = new ByteArrayOutputStream(); + cmd.errs = errs; + boolean seenHelp = TextBuiltin.containsHelp(argv); if (cmd.requiresRepository()) cmd.init(db, null); else @@ -133,9 +137,22 @@ public static byte[] rawExecute(String str, Repository db) try { cmd.execute(bean.getArguments().toArray( new String[bean.getArguments().size()])); + } catch (TerminatedByHelpException e) { + seenHelp = true; + // this is not a failure, command execution should just not happen } finally { - if (cmd.outw != null) + if (cmd.outw != null) { cmd.outw.flush(); + } + if (cmd.errw != null) { + cmd.errw.flush(); + } + if (seenHelp) { + return errs.toByteArray(); + } else if (errs.size() > 0) { + // forward the errors to the standard err + System.err.print(errs.toString()); + } } return baos.toByteArray(); } @@ -195,4 +212,14 @@ else if (r.length() > 0) { return list.toArray(new String[list.size()]); } + static class TestCmdLineParser extends CmdLineParser { + public TestCmdLineParser(Object bean) { + super(bean); + } + + @Override + protected boolean containsHelp(String... args) { + return false; + } + } } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java index 6352a2652..1c8ba0c2f 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java @@ -43,6 +43,10 @@ package org.eclipse.jgit.pgm; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.CLIRepositoryTestCase; @@ -103,4 +107,22 @@ public void testDescribeTagLong() throws Exception { assertArrayEquals(new String[] { "v1.0-0-g6fd41be", "" }, execute("git describe --long HEAD")); } + + @Test + public void testHelpArgumentBeforeUnknown() throws Exception { + String[] output = execute("git describe -h -XYZ"); + String all = Arrays.toString(output); + assertTrue("Unexpected help output: " + all, + all.contains("jgit describe")); + assertFalse("Unexpected help output: " + all, all.contains("fatal")); + } + + @Test + public void testHelpArgumentAfterUnknown() throws Exception { + String[] output = execute("git describe -XYZ -h"); + String all = Arrays.toString(output); + assertTrue("Unexpected help output: " + all, + all.contains("jgit describe")); + assertTrue("Unexpected help output: " + all, all.contains("fatal")); + } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java index 9c72b4aad..d04cef0c7 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java @@ -170,7 +170,7 @@ protected void run(final String[] argv) { } private void execute(final String[] argv) throws Exception { - final CmdLineParser clp = new CmdLineParser(this); + final CmdLineParser clp = new SubcommandLineParser(this); PrintWriter writer = new PrintWriter(System.err); try { clp.parseArgument(argv); @@ -335,4 +335,19 @@ private static void configureHttpProxy() throws MalformedURLException { } } } + + /** + * Parser for subcommands which doesn't stop parsing on help options and so + * proceeds all specified options + */ + static class SubcommandLineParser extends CmdLineParser { + public SubcommandLineParser(Object bean) { + super(bean); + } + + @Override + protected boolean containsHelp(String... args) { + return false; + } + } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java index 70868e920..24916bd1c 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java @@ -144,7 +144,7 @@ protected void run() throws Exception { } @Override - public void printUsageAndExit(final String message, final CmdLineParser clp) + public void printUsage(final String message, final CmdLineParser clp) throws IOException { errw.println(message); errw.println("jgit remote [--verbose (-v)] [--help (-h)]"); //$NON-NLS-1$ @@ -160,7 +160,6 @@ public void printUsageAndExit(final String message, final CmdLineParser clp) errw.println(); errw.flush(); - throw die(true); } private void print(List remotes) throws IOException { diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java index 40a42e520..95938326f 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java @@ -212,17 +212,20 @@ public final void execute(String[] args) throws Exception { */ protected void parseArguments(final String[] args) throws IOException { final CmdLineParser clp = new CmdLineParser(this); + help = containsHelp(args); try { clp.parseArgument(args); } catch (CmdLineException err) { - if (!help) { - this.errw.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage())); - throw die(true, err); + this.errw.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage())); + if (help) { + printUsage("", clp); //$NON-NLS-1$ } + throw die(true, err); } if (help) { - printUsageAndExit(clp); + printUsage("", clp); //$NON-NLS-1$ + throw new TerminatedByHelpException(); } argWalk = clp.getRevWalkGently(); @@ -246,6 +249,20 @@ public void printUsageAndExit(final CmdLineParser clp) throws IOException { * @throws IOException */ public void printUsageAndExit(final String message, final CmdLineParser clp) throws IOException { + printUsage(message, clp); + throw die(true); + } + + /** + * @param message + * non null + * @param clp + * parser used to print options + * @throws IOException + * @since 4.2 + */ + protected void printUsage(final String message, final CmdLineParser clp) + throws IOException { errw.println(message); errw.print("jgit "); //$NON-NLS-1$ errw.print(commandName); @@ -257,7 +274,6 @@ public void printUsageAndExit(final String message, final CmdLineParser clp) thr errw.println(); errw.flush(); - throw die(true); } /** @@ -346,4 +362,36 @@ else if (abbreviateRemote && dst.startsWith(R_REMOTES)) dst = dst.substring(R_REMOTES.length()); return dst; } + + /** + * @param args + * non null + * @return true if the given array contains help option + * @since 4.2 + */ + public static boolean containsHelp(String[] args) { + for (String str : args) { + if (str.equals("-h") || str.equals("--help")) { //$NON-NLS-1$ //$NON-NLS-2$ + return true; + } + } + return false; + } + + /** + * Exception thrown by {@link TextBuiltin} if it proceeds 'help' option + * + * @since 4.2 + */ + public static class TerminatedByHelpException extends Die { + private static final long serialVersionUID = 1L; + + /** + * Default constructor + */ + public TerminatedByHelpException() { + super(true); + } + + } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java index 3f77aa668..f794f91ca 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java @@ -45,15 +45,10 @@ import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.CmdLineException; -import org.kohsuke.args4j.IllegalAnnotationError; -import org.kohsuke.args4j.NamedOptionDef; -import org.kohsuke.args4j.Option; -import org.kohsuke.args4j.OptionDef; -import org.kohsuke.args4j.spi.OptionHandler; -import org.kohsuke.args4j.spi.Setter; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.TextBuiltin; @@ -63,6 +58,14 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.IllegalAnnotationError; +import org.kohsuke.args4j.NamedOptionDef; +import org.kohsuke.args4j.Option; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Setter; /** * Extended command line parser which handles --foo=value arguments. @@ -86,6 +89,8 @@ public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser { private RevWalk walk; + private boolean seenHelp; + /** * Creates a new command line owner that parses arguments/options and set * them into the given object. @@ -143,9 +148,58 @@ public void parseArgument(final String... args) throws CmdLineException { } tmp.add(str); + + if (containsHelp(args)) { + // suppress exceptions on required parameters if help is present + seenHelp = true; + // stop argument parsing here + break; + } + } + List backup = null; + if (seenHelp) { + backup = unsetRequiredOptions(); } - super.parseArgument(tmp.toArray(new String[tmp.size()])); + try { + super.parseArgument(tmp.toArray(new String[tmp.size()])); + } finally { + // reset "required" options to defaults for correct command printout + if (backup != null && !backup.isEmpty()) { + restoreRequiredOptions(backup); + } + seenHelp = false; + } + } + + private List unsetRequiredOptions() { + List options = getOptions(); + List backup = new ArrayList<>(options); + for (Iterator iterator = options.iterator(); iterator + .hasNext();) { + OptionHandler handler = iterator.next(); + if (handler.option instanceof NamedOptionDef + && handler.option.required()) { + iterator.remove(); + } + } + return backup; + } + + private void restoreRequiredOptions(List backup) { + List options = getOptions(); + options.clear(); + options.addAll(backup); + } + + /** + * @param args + * non null + * @return true if the given array contains help option + * @since 4.2 + */ + protected boolean containsHelp(final String... args) { + return TextBuiltin.containsHelp(args); } /** @@ -181,7 +235,7 @@ public RevWalk getRevWalkGently() { return walk; } - static class MyOptionDef extends OptionDef { + class MyOptionDef extends OptionDef { public MyOptionDef(OptionDef o) { super(o.usage(), o.metaVar(), o.required(), o.handler(), o @@ -201,6 +255,11 @@ public String toString() { return metaVar(); } } + + @Override + public boolean required() { + return seenHelp ? false : super.required(); + } } @Override @@ -211,4 +270,22 @@ protected OptionHandler createOptionHandler(OptionDef o, Setter setter) { return super.createOptionHandler(new MyOptionDef(o), setter); } + + @SuppressWarnings("unchecked") + private List getOptions() { + List options = null; + try { + Field field = org.kohsuke.args4j.CmdLineParser.class + .getDeclaredField("options"); //$NON-NLS-1$ + field.setAccessible(true); + options = (List) field.get(this); + } catch (NoSuchFieldException | SecurityException + | IllegalArgumentException | IllegalAccessException e) { + // ignore + } + if (options == null) { + return Collections.emptyList(); + } + return options; + } } From 888f08b048c213d3cbd8bb6a260d0924e82d65e1 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Mon, 28 Dec 2015 18:03:45 +0100 Subject: [PATCH 30/89] Un-ignored existing CLI tests which run just fine on Java 7+ Change-Id: I5ef334a49fb2d88d5e856b443687f3dcb126a77a Signed-off-by: Andrey Loskutov --- .../tst/org/eclipse/jgit/pgm/AddTest.java | 6 ------ .../tst/org/eclipse/jgit/pgm/ArchiveTest.java | 9 ++------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java index 4253080a6..ca3f00f49 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java @@ -46,14 +46,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import java.lang.Exception; -import java.lang.String; - import org.eclipse.jgit.api.Git; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.lib.CLIRepositoryTestCase; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; public class AddTest extends CLIRepositoryTestCase { @@ -66,14 +62,12 @@ public void setUp() throws Exception { git = new Git(db); } - @Ignore("args4j exit()s on error instead of throwing, JVM goes down") @Test public void testAddNothing() throws Exception { assertEquals("fatal: Argument \"filepattern\" is required", // execute("git add")[0]); } - @Ignore("args4j exit()s for --help, too") @Test public void testAddUsage() throws Exception { execute("git add --help"); diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java index 4222a2dcc..9aca2d823 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java @@ -52,17 +52,15 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.InputStreamReader; import java.io.IOException; +import java.io.InputStreamReader; import java.io.OutputStream; -import java.lang.Object; -import java.lang.String; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; -import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -71,9 +69,7 @@ import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.lib.CLIRepositoryTestCase; import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.pgm.CLIGitCommand; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; public class ArchiveTest extends CLIRepositoryTestCase { @@ -89,7 +85,6 @@ public void setUp() throws Exception { emptyTree = db.resolve("HEAD^{tree}").abbreviate(12).name(); } - @Ignore("Some versions of java.util.zip refuse to write an empty ZIP") @Test public void testEmptyArchive() throws Exception { byte[] result = CLIGitCommand.rawExecute( From c1b31b3f3bcb445a36ee00035b3c17446073820d Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Mon, 28 Dec 2015 18:14:05 +0100 Subject: [PATCH 31/89] repo command: properly name the required 'path' argument Fixes point 4 in bug 484951, where "jgit repo" or "jgit repo -h" dumps a stack trace. Bug: 484951 Change-Id: Ic8b362e07a40ad923dc9acde0c0983a1e7932a02 Signed-off-by: Andrey Loskutov --- .../tst/org/eclipse/jgit/pgm/RepoTest.java | 24 +++++++++++++++++++ .../src/org/eclipse/jgit/pgm/Repo.java | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java index 90efae286..85fc1dbb4 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java @@ -42,10 +42,13 @@ */ package org.eclipse.jgit.pgm; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.File; +import java.util.Arrays; + import org.eclipse.jgit.api.Git; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.lib.CLIRepositoryTestCase; @@ -97,6 +100,27 @@ public void setUp() throws Exception { resolveRelativeUris(); } + @Test + public void testMissingPath() throws Exception { + assertEquals("fatal: Argument \"path\" is required", + execute("git repo")[0]); + } + + /** + * See bug 484951: "git repo -h" should not print unexpected values + * + * @throws Exception + */ + @Test + public void testZombieHelpArgument() throws Exception { + String[] output = execute("git repo -h"); + String all = Arrays.toString(output); + assertTrue("Unexpected help output: " + all, + all.contains("jgit repo")); + assertFalse("Unexpected help output: " + all, + all.contains("jgit repo VAL")); + } + @Test public void testAddRepoManifest() throws Exception { StringBuilder xmlContent = new StringBuilder(); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java index db88008e1..ea59527fe 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Repo.java @@ -55,7 +55,7 @@ class Repo extends TextBuiltin { @Option(name = "--groups", aliases = { "-g" }, usage = "usage_groups") private String groups = "default"; //$NON-NLS-1$ - @Argument(required = true, usage = "usage_pathToXml") + @Argument(required = true, metaVar = "metaVar_path", usage = "usage_pathToXml") private String path; @Option(name = "--record-remote-branch", usage = "usage_branches") From 73cadb2eaa75dffdd92b7a63e47d605dbc749e33 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Tue, 29 Dec 2015 13:18:12 +0100 Subject: [PATCH 32/89] status command: consume more then one argument after -- See bug 484951 comment 4: "jgit status -- a b" doesn't work and complains that "b" is not an allowed argument Bug: 484951 Change-Id: I86b81e7f2bab6e928bb8e973bd50c8f4b9c6fecf Signed-off-by: Andrey Loskutov --- org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java index be82d070f..6a6322131 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java @@ -59,8 +59,9 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.internal.CLIText; +import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; - +import org.kohsuke.args4j.spi.RestOfArgumentsHandler; import org.eclipse.jgit.pgm.opt.UntrackedFilesHandler; /** @@ -83,7 +84,8 @@ class Status extends TextBuiltin { @Option(name = "--untracked-files", aliases = { "-u", "-uno", "-uall" }, usage = "usage_untrackedFilesMode", handler = UntrackedFilesHandler.class) protected String untrackedFilesMode = "all"; // default value //$NON-NLS-1$ - @Option(name = "--", metaVar = "metaVar_path", multiValued = true) + @Argument(required = false, index = 0, metaVar = "metaVar_paths") + @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class) protected List filterPaths; @Override From 0505657d6a7dd4575a64ddfb5c0928870fe5843b Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Tue, 29 Dec 2015 13:58:58 +0100 Subject: [PATCH 33/89] commit command: allow to specify path(s) argument(s) This fixes the command below: jgit commit a -m "added file a" which currently fails with: org.eclipse.jgit.api.errors.JGitInternalException: The combination of arguments --all and --only is not allowed Bug: 484973 Change-Id: I37a4ccd68101a66520ef99110f7aa0cbdcc8beba Signed-off-by: Andrey Loskutov --- .../tst/org/eclipse/jgit/pgm/CommitTest.java | 113 ++++++++++++++++++ .../org/eclipse/jgit/api/CommitCommand.java | 2 +- 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java new file mode 100644 index 000000000..360ee5a6e --- /dev/null +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015, Andrey Loskutov + * 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.junit.Assert.assertTrue; + +import org.eclipse.jgit.lib.CLIRepositoryTestCase; +import org.junit.Test; + +public class CommitTest extends CLIRepositoryTestCase { + + @Test + public void testCommitPath() throws Exception { + writeTrashFile("a", "a"); + writeTrashFile("b", "a"); + String result = toString(execute("git add a")); + assertTrue("Unexpected output: " + result, result.isEmpty()); + + result = toString(execute("git status -- a")); + assertTrue("Unexpected output: " + result, + result.contains("new file: a")); + + result = toString(execute("git status -- b")); + assertTrue("Unexpected output: " + result, + result.trim().contains("Untracked files:\n b")); + + result = toString(execute("git commit a -m 'added a'")); + assertTrue("Unexpected output: " + result, result.contains("added a")); + + result = toString(execute("git status -- a")); + assertTrue("Unexpected output: " + result, + result.trim().equals("On branch master")); + + result = toString(execute("git status -- b")); + assertTrue("Unexpected output: " + result, + result.trim().contains("Untracked files:\n b")); + } + + @Test + public void testCommitAll() throws Exception { + writeTrashFile("a", "a"); + writeTrashFile("b", "a"); + String result = toString(execute("git add a b")); + assertTrue("Unexpected output: " + result, result.isEmpty()); + + result = toString(execute("git status -- a b")); + assertTrue("Unexpected output: " + result, + result.contains("new file: a")); + assertTrue("Unexpected output: " + result, + result.contains("new file: b")); + + result = toString(execute("git commit -m 'added a b'")); + assertTrue("Unexpected output: " + result, + result.contains("added a b")); + + result = toString(execute("git status -- a b")); + assertTrue("Unexpected output: " + result, + result.trim().equals("On branch master")); + } + + String toString(String[] arr) { + StringBuilder sb = new StringBuilder(); + for (String s : arr) { + if (s != null && !s.isEmpty()) { + sb.append(s); + if (!s.endsWith("\n")) { + sb.append('\n'); + } + } + } + return sb.toString(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index 9466dab74..5b7d84f73 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -681,7 +681,7 @@ public PersonIdent getAuthor() { */ public CommitCommand setAll(boolean all) { checkCallable(); - if (!only.isEmpty()) + if (all && !only.isEmpty()) throw new JGitInternalException(MessageFormat.format( JGitText.get().illegalCombinationOfArguments, "--all", //$NON-NLS-1$ "--only")); //$NON-NLS-1$ From 97b4c02cdaa3a62764a7407e1cab1b16d984d9de Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Mon, 28 Dec 2015 21:59:01 +0100 Subject: [PATCH 34/89] reset command: provide convenient and meaningful options help This commit changes the jgit "reset" command line options help from this: jgit reset name [VAL ...] [-- path ... ...] [--hard] [--help (-h)] [--mixed] [--soft] name : Reset current HEAD to the specified state [...] to this: jgit reset [commit-ish] [path ... ...] [-- path ... ...] [--hard] [--help (-h)] [--mixed] [--soft] commit-ish : Reset to given reference name [...] Bug: 484951 Change-Id: I614e71101b4f9f46ef8f02379d1a9d135f3292d2 Signed-off-by: Andrey Loskutov --- .../tst/org/eclipse/jgit/pgm/ResetTest.java | 32 ++++++++++++++++--- .../jgit/pgm/internal/CLIText.properties | 1 + .../src/org/eclipse/jgit/pgm/Reset.java | 10 +++--- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java index dae477928..8cdd45ac7 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java @@ -48,6 +48,7 @@ import org.eclipse.jgit.lib.CLIRepositoryTestCase; import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; public class ResetTest extends CLIRepositoryTestCase { @@ -61,6 +62,13 @@ public void setUp() throws Exception { git = new Git(db); } + @Test + public void testZombieArgument_Bug484951() throws Exception { + String[] result = execute("git reset -h"); + assertFalse("Unexpected argument: " + result[0], + result[0].contains("[VAL ...]")); + } + @Test public void testResetSelf() throws Exception { RevCommit commit = git.commit().setMessage("initial commit").call(); @@ -91,15 +99,28 @@ public void testResetEmptyPath() throws Exception { @Test public void testResetPathDoubleDash() throws Exception { - resetPath(true); + resetPath(true, true); } @Test public void testResetPathNoDoubleDash() throws Exception { - resetPath(false); + resetPath(false, true); } - private void resetPath(boolean useDoubleDash) throws Exception { + @Test + public void testResetPathDoubleDashNoRef() throws Exception { + resetPath(true, false); + } + + @Ignore("Currently we cannote recognize if a name is a commit-ish or a path, " + + "so 'git reset a' will not work if 'a' is not a branch name but a file path") + @Test + public void testResetPathNoDoubleDashNoRef() throws Exception { + resetPath(false, false); + } + + private void resetPath(boolean useDoubleDash, boolean supplyCommit) + throws Exception { // create files a and b writeTrashFile("a", "Hello world a"); writeTrashFile("b", "Hello world b"); @@ -115,8 +136,9 @@ private void resetPath(boolean useDoubleDash) throws Exception { git.add().addFilepattern(".").call(); // reset only file a - String cmd = String.format("git reset %s%s a", commit.getId().name(), - (useDoubleDash) ? " --" : ""); + String cmd = String.format("git reset %s%s a", + supplyCommit ? commit.getId().name() : "", + useDoubleDash ? " --" : ""); assertStringArrayEquals("", execute(cmd)); assertEquals(commit.getId(), git.getRepository().exactRef("HEAD").getObjectId()); diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties index 335336da2..3dcbda3b9 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties @@ -337,6 +337,7 @@ usage_recordChangesToRepository=Record changes to the repository usage_recurseIntoSubtrees=recurse into subtrees usage_renameLimit=limit size of rename matrix usage_reset=Reset current HEAD to the specified state +usage_resetReference=Reset to given reference name usage_resetHard=Resets the index and working tree usage_resetSoft=Resets without touching the index file nor the working tree usage_resetMixed=Resets the index but not the working tree diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java index 6ba076290..eb222747d 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Reset.java @@ -51,7 +51,7 @@ import org.eclipse.jgit.api.ResetCommand.ResetType; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; -import org.kohsuke.args4j.spi.StopOptionHandler; +import org.kohsuke.args4j.spi.RestOfArgumentsHandler; @Command(common = true, usage = "usage_reset") class Reset extends TextBuiltin { @@ -65,12 +65,12 @@ class Reset extends TextBuiltin { @Option(name = "--hard", usage = "usage_resetHard") private boolean hard = false; - @Argument(required = true, index = 0, metaVar = "metaVar_name", usage = "usage_reset") + @Argument(required = false, index = 0, metaVar = "metaVar_commitish", usage = "usage_resetReference") private String commit; - @Argument(index = 1) - @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = StopOptionHandler.class) - private List paths = new ArrayList(); + @Argument(required = false, index = 1, metaVar = "metaVar_paths") + @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = RestOfArgumentsHandler.class) + private List paths = new ArrayList<>(); @Override protected void run() throws Exception { From aabbc58341c2381a2c5907b82c50691a296ae6c9 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Tue, 29 Dec 2015 14:27:37 +0100 Subject: [PATCH 35/89] Sort "eager" path-like options to the end of the help The "--" path option (and all other similar options consuming all remaining arguments) should be placed at the end of the command line help. Currently jgit reset -h shows this: jgit reset [commit-ish] [path ... ...] [-- path ... ...] [--hard] [--help (-h)] [--mixed] [--soft] After the patch the help shows this: jgit reset [commit-ish] [path ... ...] [--hard] [--help (-h)] [--mixed] [--soft] [-- path ... ...] Bug: 484951 Change-Id: I3db332bf293ca8d6bfaab0d546cd35af689bd46e Signed-off-by: Andrey Loskutov --- .../tst/org/eclipse/jgit/pgm/ResetTest.java | 7 ++++ .../tst/org/eclipse/jgit/pgm/StatusTest.java | 8 +++++ .../eclipse/jgit/pgm/opt/CmdLineParser.java | 36 +++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java index 8cdd45ac7..16c5889c4 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java @@ -62,6 +62,13 @@ public void setUp() throws Exception { git = new Git(db); } + @Test + public void testPathOptionHelp() throws Exception { + String[] result = execute("git reset -h"); + assertTrue("Unexpected argument: " + result[1], + result[1].endsWith("[-- path ... ...]")); + } + @Test public void testZombieArgument_Bug484951() throws Exception { String[] result = execute("git reset -h"); diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java index 854c52d88..368047c60 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java @@ -44,6 +44,7 @@ import static org.eclipse.jgit.lib.Constants.MASTER; import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.junit.Assert.assertTrue; import java.io.IOException; @@ -55,6 +56,13 @@ public class StatusTest extends CLIRepositoryTestCase { + @Test + public void testPathOptionHelp() throws Exception { + String[] result = execute("git status -h"); + assertTrue("Unexpected argument: " + result[1], + result[1].endsWith("[-- path ... ...]")); + } + @Test public void testStatusDefault() throws Exception { executeTest("git status", false, true); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java index f794f91ca..66d481642 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java @@ -43,11 +43,13 @@ package org.eclipse.jgit.pgm.opt; +import java.io.Writer; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.ResourceBundle; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; @@ -65,6 +67,7 @@ import org.kohsuke.args4j.Option; import org.kohsuke.args4j.OptionDef; import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.RestOfArgumentsHandler; import org.kohsuke.args4j.spi.Setter; /** @@ -288,4 +291,37 @@ private List getOptions() { } return options; } + + @Override + public void printSingleLineUsage(Writer w, ResourceBundle rb) { + List options = getOptions(); + if (options.isEmpty()) { + super.printSingleLineUsage(w, rb); + return; + } + List backup = new ArrayList<>(options); + boolean changed = sortRestOfArgumentsHandlerToTheEnd(options); + try { + super.printSingleLineUsage(w, rb); + } finally { + if (changed) { + options.clear(); + options.addAll(backup); + } + } + } + + private boolean sortRestOfArgumentsHandlerToTheEnd( + List options) { + for (int i = 0; i < options.size(); i++) { + OptionHandler handler = options.get(i); + if (handler instanceof RestOfArgumentsHandler + || handler instanceof PathTreeFilterHandler) { + options.remove(i); + options.add(handler); + return true; + } + } + return false; + } } From 3776b14ab45ca1daf0771fa1a8245bc097ec2e12 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Thu, 24 Dec 2015 14:27:50 -0800 Subject: [PATCH 36/89] AddCommand: Use NameConflictTreeWalk to identify file-dir changes Adding a path that already exists but is changing type such as from symlink to subdirectory requires a NameConflictTreeWalk to match up the two different entry types that share the same name. NameConflictTreeWalk needs a bug fix to pop conflicting entries when PathFilterGroup aborts the walk early so that it does not allow DirCacheBuilderIterator to copy conflicting entries into the output cache. Change-Id: I61b49cbe949ca8b4b98f9eb6dbe7b1f82eabb724 --- .../org/eclipse/jgit/api/AddCommandTest.java | 98 ++++++++++++++++++- .../src/org/eclipse/jgit/api/AddCommand.java | 23 ++++- .../jgit/dircache/DirCacheBuildIterator.java | 5 + .../jgit/treewalk/AbstractTreeIterator.java | 8 ++ .../jgit/treewalk/EmptyTreeIterator.java | 5 + .../jgit/treewalk/NameConflictTreeWalk.java | 37 +++++++ .../org/eclipse/jgit/treewalk/TreeWalk.java | 26 ++++- 7 files changed, 192 insertions(+), 10 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java index a5ad18d10..29fc3c3a0 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java @@ -43,6 +43,7 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.util.FileUtils.RECURSIVE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -777,12 +778,107 @@ public void testAssumeUnchanged() throws Exception { assertEquals("[a.txt, mode:100644, content:more content," + " assume-unchanged:false][b.txt, mode:100644," - + "" + "" + " content:content, assume-unchanged:true]", indexState(CONTENT | ASSUME_UNCHANGED)); } + @Test + public void testReplaceFileWithDirectory() + throws IOException, NoFilepatternException, GitAPIException { + try (Git git = new Git(db)) { + writeTrashFile("df", "before replacement"); + git.add().addFilepattern("df").call(); + assertEquals("[df, mode:100644, content:before replacement]", + indexState(CONTENT)); + FileUtils.delete(new File(db.getWorkTree(), "df")); + writeTrashFile("df/f", "after replacement"); + git.add().addFilepattern("df").call(); + assertEquals("[df/f, mode:100644, content:after replacement]", + indexState(CONTENT)); + } + } + + @Test + public void testReplaceDirectoryWithFile() + throws IOException, NoFilepatternException, GitAPIException { + try (Git git = new Git(db)) { + writeTrashFile("df/f", "before replacement"); + git.add().addFilepattern("df").call(); + assertEquals("[df/f, mode:100644, content:before replacement]", + indexState(CONTENT)); + FileUtils.delete(new File(db.getWorkTree(), "df"), RECURSIVE); + writeTrashFile("df", "after replacement"); + git.add().addFilepattern("df").call(); + assertEquals("[df, mode:100644, content:after replacement]", + indexState(CONTENT)); + } + } + + @Test + public void testReplaceFileByPartOfDirectory() + throws IOException, NoFilepatternException, GitAPIException { + try (Git git = new Git(db)) { + writeTrashFile("src/main", "df", "before replacement"); + writeTrashFile("src/main", "z", "z"); + writeTrashFile("z", "z2"); + git.add().addFilepattern("src/main/df") + .addFilepattern("src/main/z") + .addFilepattern("z") + .call(); + assertEquals( + "[src/main/df, mode:100644, content:before replacement]" + + "[src/main/z, mode:100644, content:z]" + + "[z, mode:100644, content:z2]", + indexState(CONTENT)); + FileUtils.delete(new File(db.getWorkTree(), "src/main/df")); + writeTrashFile("src/main/df", "a", "after replacement"); + writeTrashFile("src/main/df", "b", "unrelated file"); + git.add().addFilepattern("src/main/df/a").call(); + assertEquals( + "[src/main/df/a, mode:100644, content:after replacement]" + + "[src/main/z, mode:100644, content:z]" + + "[z, mode:100644, content:z2]", + indexState(CONTENT)); + } + } + + @Test + public void testReplaceDirectoryConflictsWithFile() + throws IOException, NoFilepatternException, GitAPIException { + DirCache dc = db.lockDirCache(); + try (ObjectInserter oi = db.newObjectInserter()) { + DirCacheBuilder builder = dc.builder(); + File f = writeTrashFile("a", "df", "content"); + addEntryToBuilder("a", f, oi, builder, 1); + + f = writeTrashFile("a", "df", "other content"); + addEntryToBuilder("a/df", f, oi, builder, 3); + + f = writeTrashFile("a", "df", "our content"); + addEntryToBuilder("a/df", f, oi, builder, 2); + + f = writeTrashFile("z", "z"); + addEntryToBuilder("z", f, oi, builder, 0); + builder.commit(); + } + assertEquals( + "[a, mode:100644, stage:1, content:content]" + + "[a/df, mode:100644, stage:2, content:our content]" + + "[a/df, mode:100644, stage:3, content:other content]" + + "[z, mode:100644, content:z]", + indexState(CONTENT)); + + try (Git git = new Git(db)) { + FileUtils.delete(new File(db.getWorkTree(), "a"), RECURSIVE); + writeTrashFile("a", "merged"); + git.add().addFilepattern("a").call(); + assertEquals("[a, mode:100644, content:merged]" + + "[z, mode:100644, content:z]", + indexState(CONTENT)); + } + } + @Test public void testExecutableRetention() throws Exception { StoredConfig config = db.getConfig(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java index 8a2c08c80..3b94f16f1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java @@ -45,6 +45,7 @@ import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.eclipse.jgit.lib.FileMode.GITLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; import java.io.IOException; import java.io.InputStream; @@ -66,7 +67,7 @@ import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.FileTreeIterator; -import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.NameConflictTreeWalk; import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; @@ -141,7 +142,7 @@ public DirCache call() throws GitAPIException, NoFilepatternException { boolean addAll = filepatterns.contains("."); //$NON-NLS-1$ try (ObjectInserter inserter = repo.newObjectInserter(); - final TreeWalk tw = new TreeWalk(repo)) { + NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) { tw.setOperationType(OperationType.CHECKIN_OP); dc = repo.lockDirCache(); @@ -151,7 +152,6 @@ public DirCache call() throws GitAPIException, NoFilepatternException { workingTreeIterator = new FileTreeIterator(repo); workingTreeIterator.setDirCacheIterator(tw, 0); tw.addTree(workingTreeIterator); - tw.setRecursive(true); if (!addAll) tw.setFilter(PathFilterGroup.createFromStrings(filepatterns)); @@ -180,9 +180,14 @@ public DirCache call() throws GitAPIException, NoFilepatternException { continue; } + if (tw.isSubtree() && !tw.isDirectoryFileConflict()) { + tw.enterSubtree(); + continue; + } + if (f == null) { // working tree file does not exist - if (c != null - && (!update || GITLINK == c.getEntryFileMode())) { + if (entry != null + && (!update || GITLINK == entry.getFileMode())) { builder.add(entry); } continue; @@ -196,6 +201,14 @@ public DirCache call() throws GitAPIException, NoFilepatternException { continue; } + if (f.getEntryRawMode() == TYPE_TREE) { + // Index entry exists and is symlink, gitlink or file, + // otherwise the tree would have been entered above. + // Replace the index entry by diving into tree of files. + tw.enterSubtree(); + continue; + } + byte[] path = tw.getRawPath(); if (entry == null || entry.getStage() > 0) { entry = new DirCacheEntry(path); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java index da5530666..c10e41608 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java @@ -130,4 +130,9 @@ public void stopWalk() { if (cur < cnt) builder.keep(cur, cnt - cur); } + + @Override + protected boolean needsStopWalk() { + return ptr < cache.getEntryCount(); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java index 5e7188957..27d0f7b58 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java @@ -691,6 +691,14 @@ public void stopWalk() { // Do nothing by default. Most iterators do not care. } + /** + * @return true if the iterator implements {@link #stopWalk()}. + * @since 4.2 + */ + protected boolean needsStopWalk() { + return false; + } + /** * @return the length of the name component of the path for the current entry */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java index 8dbf80e6a..ec4a84eff 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java @@ -142,4 +142,9 @@ public void stopWalk() { if (parent != null) parent.stopWalk(); } + + @Override + protected boolean needsStopWalk() { + return parent != null && parent.needsStopWalk(); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java index 350f56396..d2195a874 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java @@ -43,6 +43,8 @@ package org.eclipse.jgit.treewalk; +import java.io.IOException; + import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.lib.FileMode; @@ -338,6 +340,41 @@ void skipEntriesEqual() throws CorruptObjectException { dfConflict = null; } + void stopWalk() throws IOException { + if (!needsStopWalk()) { + return; + } + + // Name conflicts make aborting early difficult. Multiple paths may + // exist between the file and directory versions of a name. To ensure + // the directory version is skipped over (as it was previously visited + // during the file version step) requires popping up the stack and + // finishing out each subtree that the walker dove into. Siblings in + // parents do not need to be recursed into, bounding the cost. + for (;;) { + AbstractTreeIterator t = min(); + if (t.eof()) { + if (depth > 0) { + exitSubtree(); + popEntriesEqual(); + continue; + } + return; + } + currentHead = t; + skipEntriesEqual(); + } + } + + private boolean needsStopWalk() { + for (AbstractTreeIterator t : trees) { + if (t.needsStopWalk()) { + return true; + } + } + return false; + } + /** * True if the current entry is covered by a directory/file conflict. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java index 438549520..83fada4f9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java @@ -57,6 +57,7 @@ import org.eclipse.jgit.attributes.AttributesNode; import org.eclipse.jgit.attributes.AttributesNodeProvider; import org.eclipse.jgit.attributes.AttributesProvider; +import org.eclipse.jgit.dircache.DirCacheBuildIterator; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; @@ -256,7 +257,7 @@ public static TreeWalk forPath(final Repository db, final String path, private boolean postOrderTraversal; - private int depth; + int depth; private boolean advance; @@ -665,12 +666,29 @@ public boolean next() throws MissingObjectException, return true; } } catch (StopWalkException stop) { - for (final AbstractTreeIterator t : trees) - t.stopWalk(); + stopWalk(); return false; } } + /** + * Notify iterators the walk is aborting. + *

+ * Primarily to notify {@link DirCacheBuildIterator} the walk is aborting so + * that it can copy any remaining entries. + * + * @throws IOException + * if traversal of remaining entries throws an exception during + * object access. This should never occur as remaining trees + * should already be in memory, however the methods used to + * finish traversal are declared to throw IOException. + */ + void stopWalk() throws IOException { + for (AbstractTreeIterator t : trees) { + t.stopWalk(); + } + } + /** * Obtain the tree iterator for the current entry. *

@@ -1065,7 +1083,7 @@ void skipEntriesEqual() throws CorruptObjectException { } } - private void exitSubtree() { + void exitSubtree() { depth--; for (int i = 0; i < trees.length; i++) trees[i] = trees[i].parent; From b71ba69410db5e50dd10c5c40e96699c9de7e5e8 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Mon, 28 Dec 2015 11:43:07 -0800 Subject: [PATCH 37/89] DirCacheEditor: Replace file-with-tree and tree-with-file If a PathEdit tries to store a file where a subtree was, or a subtree where a file was, replace the entry in the DirCache with the new name(s). This supports switching between file and tree entry types using a DirCacheEditor. Add new unit tests to cover the conditions where these can happen. Change-Id: Ie843d9388825f9e3d918a5666aa04e47cd6306e7 --- .../jgit/dircache/DirCachePathEditTest.java | 58 +++++++ .../jgit/lib/DirCacheCheckoutTest.java | 6 +- .../jgit/dircache/BaseDirCacheEditor.java | 24 +++ .../org/eclipse/jgit/dircache/DirCache.java | 7 +- .../eclipse/jgit/dircache/DirCacheEditor.java | 162 +++++++++++++++++- .../eclipse/jgit/dircache/DirCacheEntry.java | 2 +- 6 files changed, 246 insertions(+), 13 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java index 63ec85861..3988f6a4c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java @@ -154,6 +154,64 @@ public void testPathEditShouldBeCalledForEachStage() throws Exception { assertEquals(DirCacheEntry.STAGE_3, entries.get(2).getStage()); } + @Test + public void testFileReplacesTree() throws Exception { + DirCache dc = DirCache.newInCore(); + DirCacheEditor editor = dc.editor(); + editor.add(new AddEdit("a")); + editor.add(new AddEdit("b/c")); + editor.add(new AddEdit("b/d")); + editor.add(new AddEdit("e")); + editor.finish(); + + editor = dc.editor(); + editor.add(new AddEdit("b")); + editor.finish(); + + assertEquals(3, dc.getEntryCount()); + assertEquals("a", dc.getEntry(0).getPathString()); + assertEquals("b", dc.getEntry(1).getPathString()); + assertEquals("e", dc.getEntry(2).getPathString()); + + dc.clear(); + editor = dc.editor(); + editor.add(new AddEdit("A.c")); + editor.add(new AddEdit("A/c")); + editor.add(new AddEdit("A0c")); + editor.finish(); + + editor = dc.editor(); + editor.add(new AddEdit("A")); + editor.finish(); + assertEquals(3, dc.getEntryCount()); + assertEquals("A", dc.getEntry(0).getPathString()); + assertEquals("A.c", dc.getEntry(1).getPathString()); + assertEquals("A0c", dc.getEntry(2).getPathString()); + } + + @Test + public void testTreeReplacesFile() throws Exception { + DirCache dc = DirCache.newInCore(); + DirCacheEditor editor = dc.editor(); + editor.add(new AddEdit("a")); + editor.add(new AddEdit("ab")); + editor.add(new AddEdit("b")); + editor.add(new AddEdit("e")); + editor.finish(); + + editor = dc.editor(); + editor.add(new AddEdit("b/c/d/f")); + editor.add(new AddEdit("b/g/h/i")); + editor.finish(); + + assertEquals(5, dc.getEntryCount()); + assertEquals("a", dc.getEntry(0).getPathString()); + assertEquals("ab", dc.getEntry(1).getPathString()); + assertEquals("b/c/d/f", dc.getEntry(2).getPathString()); + assertEquals("b/g/h/i", dc.getEntry(3).getPathString()); + assertEquals("e", dc.getEntry(4).getPathString()); + } + private static DirCacheEntry createEntry(String path, int stage) { DirCacheEntry entry = new DirCacheEntry(path, stage); entry.setFileMode(FileMode.REGULAR_FILE); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java index d768e0fa0..92901f826 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java @@ -1084,7 +1084,7 @@ public void testCheckoutChangeLinkToNonEmptyDirsAndNewIndexEntry() assertWorkDir(mkmap(linkName, "a", fname, "a")); Status st = git.status().call(); - assertFalse(st.isClean()); + assertTrue(st.isClean()); } @Test @@ -1213,9 +1213,7 @@ public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry() assertWorkDir(mkmap(fname, "a")); Status st = git.status().call(); - assertFalse(st.isClean()); - assertEquals(1, st.getAdded().size()); - assertTrue(st.getAdded().contains(fname + "/dir/file1")); + assertTrue(st.isClean()); } @Test diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java index 70f80aeb7..c5c1b0d77 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java @@ -44,6 +44,8 @@ package org.eclipse.jgit.dircache; +import static org.eclipse.jgit.lib.FileMode.TREE; + import java.io.IOException; /** @@ -176,6 +178,28 @@ protected void replace() { cache.replace(entries, entryCnt); } + static int pathCompare(byte[] aPath, int aPos, int aEnd, int aMode, + byte[] bPath, int bPos, int bEnd, int bMode) { + while (aPos < aEnd && bPos < bEnd) { + int cmp = (aPath[aPos++] & 0xff) - (bPath[bPos++] & 0xff); + if (cmp != 0) { + return cmp; + } + } + + if (aPos < aEnd) { + return (aPath[aPos] & 0xff) - lastPathChar(bMode); + } + if (bPos < bEnd) { + return lastPathChar(aMode) - (bPath[bPos] & 0xff); + } + return 0; + } + + private static int lastPathChar(int mode) { + return TREE.equals(mode) ? '/' : '\0'; + } + /** * Finish, write, commit this change, and release the index lock. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java index fa0339544..ecdfe823a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java @@ -800,8 +800,11 @@ public int findEntry(final String path) { * information. If < 0 the entry does not exist in the index. * @since 3.4 */ - public int findEntry(final byte[] p, final int pLen) { - int low = 0; + public int findEntry(byte[] p, int pLen) { + return findEntry(0, p, pLen); + } + + int findEntry(int low, byte[] p, int pLen) { int high = entryCnt; while (low < high) { int mid = (low + high) >>> 1; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java index 932ef94db..889e19422 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java @@ -44,6 +44,10 @@ package org.eclipse.jgit.dircache; +import static org.eclipse.jgit.dircache.DirCache.cmp; +import static org.eclipse.jgit.dircache.DirCacheTree.peq; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; + import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; @@ -72,11 +76,12 @@ public class DirCacheEditor extends BaseDirCacheEditor { public int compare(final PathEdit o1, final PathEdit o2) { final byte[] a = o1.path; final byte[] b = o2.path; - return DirCache.cmp(a, a.length, b, b.length); + return cmp(a, a.length, b, b.length); } }; private final List edits; + private int editIdx; /** * Construct a new editor. @@ -126,37 +131,44 @@ public void finish() { private void applyEdits() { Collections.sort(edits, EDIT_CMP); + editIdx = 0; final int maxIdx = cache.getEntryCount(); int lastIdx = 0; - for (final PathEdit e : edits) { - int eIdx = cache.findEntry(e.path, e.path.length); + while (editIdx < edits.size()) { + PathEdit e = edits.get(editIdx++); + int eIdx = cache.findEntry(lastIdx, e.path, e.path.length); final boolean missing = eIdx < 0; if (eIdx < 0) eIdx = -(eIdx + 1); final int cnt = Math.min(eIdx, maxIdx) - lastIdx; if (cnt > 0) fastKeep(lastIdx, cnt); - lastIdx = missing ? eIdx : cache.nextEntry(eIdx); - if (e instanceof DeletePath) + if (e instanceof DeletePath) { + lastIdx = missing ? eIdx : cache.nextEntry(eIdx); continue; + } if (e instanceof DeleteTree) { lastIdx = cache.nextEntry(e.path, e.path.length, eIdx); continue; } if (missing) { - final DirCacheEntry ent = new DirCacheEntry(e.path); + DirCacheEntry ent = new DirCacheEntry(e.path); e.apply(ent); if (ent.getRawMode() == 0) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().fileModeNotSetForPath, ent.getPathString())); } + lastIdx = e.replace + ? deleteOverlappingSubtree(ent, eIdx) + : eIdx; fastAdd(ent); } else { // Apply to all entries of the current path (different stages) + lastIdx = cache.nextEntry(eIdx); for (int i = eIdx; i < lastIdx; i++) { final DirCacheEntry ent = cache.getEntry(i); e.apply(ent); @@ -170,6 +182,102 @@ private void applyEdits() { fastKeep(lastIdx, cnt); } + private int deleteOverlappingSubtree(DirCacheEntry ent, int eIdx) { + byte[] entPath = ent.path; + int entLen = entPath.length; + + // Delete any file that was previously processed and overlaps + // the parent directory for the new entry. Since the editor + // always processes entries in path order, binary search back + // for the overlap for each parent directory. + for (int p = pdir(entPath, entLen); p > 0; p = pdir(entPath, p)) { + int i = findEntry(entPath, p); + if (i >= 0) { + // A file does overlap, delete the file from the array. + // No other parents can have overlaps as the file should + // have taken care of that itself. + int n = --entryCnt - i; + System.arraycopy(entries, i + 1, entries, i, n); + break; + } + + // If at least one other entry already exists in this parent + // directory there is no need to continue searching up the tree. + i = -(i + 1); + if (i < entryCnt && inDir(entries[i], entPath, p)) { + break; + } + } + + int maxEnt = cache.getEntryCount(); + if (eIdx >= maxEnt) { + return maxEnt; + } + + DirCacheEntry next = cache.getEntry(eIdx); + if (pathCompare(next.path, 0, next.path.length, 0, + entPath, 0, entLen, TYPE_TREE) < 0) { + // Next DirCacheEntry sorts before new entry as tree. Defer a + // DeleteTree command to delete any entries if they exist. This + // case only happens for A, A.c, A/c type of conflicts (rare). + insertEdit(new DeleteTree(entPath)); + return eIdx; + } + + // Next entry may be contained by the entry-as-tree, skip if so. + while (eIdx < maxEnt && inDir(cache.getEntry(eIdx), entPath, entLen)) { + eIdx++; + } + return eIdx; + } + + private int findEntry(byte[] p, int pLen) { + int low = 0; + int high = entryCnt; + while (low < high) { + int mid = (low + high) >>> 1; + int cmp = cmp(p, pLen, entries[mid]); + if (cmp < 0) { + high = mid; + } else if (cmp == 0) { + while (mid > 0 && cmp(p, pLen, entries[mid - 1]) == 0) { + mid--; + } + return mid; + } else { + low = mid + 1; + } + } + return -(low + 1); + } + + private void insertEdit(DeleteTree d) { + for (int i = editIdx; i < edits.size(); i++) { + int cmp = EDIT_CMP.compare(d, edits.get(i)); + if (cmp < 0) { + edits.add(i, d); + return; + } else if (cmp == 0) { + return; + } + } + edits.add(d); + } + + private static boolean inDir(DirCacheEntry e, byte[] path, int pLen) { + return e.path.length > pLen && e.path[pLen] == '/' + && peq(path, e.path, pLen); + } + + private static int pdir(byte[] path, int e) { + for (e--; e > 0; e--) { + if (path[e] == '/') { + return e; + } + } + return 0; + } + /** * Any index record update. *

@@ -181,6 +289,7 @@ private void applyEdits() { */ public abstract static class PathEdit { final byte[] path; + boolean replace = true; /** * Create a new update command by path name. @@ -192,6 +301,10 @@ public PathEdit(final String entryPath) { path = Constants.encode(entryPath); } + PathEdit(byte[] path) { + this.path = path; + } + /** * Create a new update command for an existing entry instance. * @@ -203,6 +316,22 @@ public PathEdit(final DirCacheEntry ent) { path = ent.path; } + /** + * Configure if a file can replace a directory (or vice versa). + *

+ * Default is {@code true} as this is usually the desired behavior. + * + * @param ok + * if true a file can replace a directory, or a directory can + * replace a file. + * @return {@code this} + * @since 4.2 + */ + public PathEdit setReplace(boolean ok) { + replace = ok; + return this; + } + /** * Apply the update to a single cache entry matching the path. *

@@ -214,6 +343,12 @@ public PathEdit(final DirCacheEntry ent) { * the path is a new path in the index. */ public abstract void apply(DirCacheEntry ent); + + @Override + public String toString() { + String p = DirCacheEntry.toString(path); + return getClass().getSimpleName() + '[' + p + ']'; + } } /** @@ -281,6 +416,21 @@ public DeleteTree(String entryPath) { : entryPath + '/'); } + DeleteTree(byte[] path) { + super(appendSlash(path)); + } + + private static byte[] appendSlash(byte[] path) { + int n = path.length; + if (n > 0 && path[n - 1] != '/') { + byte[] r = new byte[n + 1]; + System.arraycopy(path, 0, r, 0, n); + r[n] = '/'; + return r; + } + return path; + } + public void apply(final DirCacheEntry ent) { throw new UnsupportedOperationException(JGitText.get().noApplyInDelete); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java index c8bc0960f..5df9e0b4d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java @@ -745,7 +745,7 @@ private static void checkPath(byte[] path) { } } - private static String toString(final byte[] path) { + static String toString(final byte[] path) { return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString(); } From 683c41af92d248c818dcddb4d86c1640eba79374 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 18 Dec 2015 14:31:20 -0800 Subject: [PATCH 38/89] DirCache: Do not create duplicate tree entries If a file (e.g. "A") and a subtree file (e.g. "A/foo.c") both appear in the DirCache this cache should not be written out as a tree object. The "A" file and "A" subtree conflict with each other in the same tree and will fail fsck. Detect this condition during DirCacheBuilder and DirCacheEditor finish() so the application can be halted early before it updates a DirCache that might later write an invalid tree structure. Change-Id: I95660787e88df336297949b383f4c5fda52e75f5 --- .../jgit/dircache/DirCachePathEditTest.java | 46 +++++++++++ .../jgit/dircache/BaseDirCacheEditor.java | 74 +++++++++++++++++ .../errors/DirCacheNameConflictException.java | 80 +++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java index 3988f6a4c..39a0cdac5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java @@ -43,11 +43,13 @@ package org.eclipse.jgit.dircache; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.errors.DirCacheNameConflictException; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.junit.Test; @@ -212,6 +214,50 @@ public void testTreeReplacesFile() throws Exception { assertEquals("e", dc.getEntry(4).getPathString()); } + @Test + public void testFileOverlapsTree() throws Exception { + DirCache dc = DirCache.newInCore(); + DirCacheEditor editor = dc.editor(); + editor.add(new AddEdit("a")); + editor.add(new AddEdit("a/b").setReplace(false)); + try { + editor.finish(); + fail("Expected DirCacheNameConflictException to be thrown"); + } catch (DirCacheNameConflictException e) { + assertEquals("a a/b", e.getMessage()); + assertEquals("a", e.getPath1()); + assertEquals("a/b", e.getPath2()); + } + + editor = dc.editor(); + editor.add(new AddEdit("A.c")); + editor.add(new AddEdit("A/c").setReplace(false)); + editor.add(new AddEdit("A0c")); + editor.add(new AddEdit("A")); + try { + editor.finish(); + fail("Expected DirCacheNameConflictException to be thrown"); + } catch (DirCacheNameConflictException e) { + assertEquals("A A/c", e.getMessage()); + assertEquals("A", e.getPath1()); + assertEquals("A/c", e.getPath2()); + } + + editor = dc.editor(); + editor.add(new AddEdit("A.c")); + editor.add(new AddEdit("A/b/c/d").setReplace(false)); + editor.add(new AddEdit("A/b/c")); + editor.add(new AddEdit("A0c")); + try { + editor.finish(); + fail("Expected DirCacheNameConflictException to be thrown"); + } catch (DirCacheNameConflictException e) { + assertEquals("A/b/c A/b/c/d", e.getMessage()); + assertEquals("A/b/c", e.getPath1()); + assertEquals("A/b/c/d", e.getPath2()); + } + } + private static DirCacheEntry createEntry(String path, int stage) { DirCacheEntry entry = new DirCacheEntry(path, stage); entry.setFileMode(FileMode.REGULAR_FILE); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java index c5c1b0d77..fc789b3d1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java @@ -45,9 +45,12 @@ package org.eclipse.jgit.dircache; import static org.eclipse.jgit.lib.FileMode.TREE; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; import java.io.IOException; +import org.eclipse.jgit.errors.DirCacheNameConflictException; + /** * Generic update/editing support for {@link DirCache}. *

@@ -170,6 +173,7 @@ protected void fastKeep(final int pos, int cnt) { * {@link #finish()}, and only after {@link #entries} is sorted. */ protected void replace() { + checkNameConflicts(); if (entryCnt < entries.length / 2) { final DirCacheEntry[] n = new DirCacheEntry[entryCnt]; System.arraycopy(entries, 0, n, 0, entryCnt); @@ -178,6 +182,76 @@ protected void replace() { cache.replace(entries, entryCnt); } + private void checkNameConflicts() { + int end = entryCnt - 1; + for (int eIdx = 0; eIdx < end; eIdx++) { + DirCacheEntry e = entries[eIdx]; + if (e.getStage() != 0) { + continue; + } + + byte[] ePath = e.path; + int prefixLen = lastSlash(ePath) + 1; + + for (int nIdx = eIdx + 1; nIdx < entryCnt; nIdx++) { + DirCacheEntry n = entries[nIdx]; + if (n.getStage() != 0) { + continue; + } + + byte[] nPath = n.path; + if (!startsWith(ePath, nPath, prefixLen)) { + // Different prefix; this entry is in another directory. + break; + } + + int s = nextSlash(nPath, prefixLen); + int m = s < nPath.length ? TYPE_TREE : n.getRawMode(); + int cmp = pathCompare( + ePath, prefixLen, ePath.length, TYPE_TREE, + nPath, prefixLen, s, m); + if (cmp < 0) { + break; + } else if (cmp == 0) { + throw new DirCacheNameConflictException( + e.getPathString(), + n.getPathString()); + } + } + } + } + + private static int lastSlash(byte[] path) { + for (int i = path.length - 1; i >= 0; i--) { + if (path[i] == '/') { + return i; + } + } + return -1; + } + + private static int nextSlash(byte[] b, int p) { + final int n = b.length; + for (; p < n; p++) { + if (b[p] == '/') { + return p; + } + } + return n; + } + + private static boolean startsWith(byte[] a, byte[] b, int n) { + if (b.length < n) { + return false; + } + for (n--; n >= 0; n--) { + if (a[n] != b[n]) { + return false; + } + } + return true; + } + static int pathCompare(byte[] aPath, int aPos, int aEnd, int aMode, byte[] bPath, int bPos, int bEnd, int bMode) { while (aPos < aEnd && bPos < bEnd) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java new file mode 100644 index 000000000..5f67e3439 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015, Google Inc. + * 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.errors; + +/** + * Thrown by DirCache code when entries overlap in impossible way. + * + * @since 4.2 + */ +public class DirCacheNameConflictException extends IllegalStateException { + private static final long serialVersionUID = 1L; + + private final String path1; + private final String path2; + + /** + * Construct an exception for a specific path. + * + * @param path1 + * one path that conflicts. + * @param path2 + * another path that conflicts. + */ + public DirCacheNameConflictException(String path1, String path2) { + super(path1 + ' ' + path2); + this.path1 = path1; + this.path2 = path2; + } + + /** @return one of the paths that has a conflict. */ + public String getPath1() { + return path1; + } + + /** @return another path that has a conflict. */ + public String getPath2() { + return path2; + } +} From 29aa444760ea729dd10cdb0468055282a59096e5 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Tue, 29 Dec 2015 15:11:21 -0800 Subject: [PATCH 39/89] PackWriter: use lib.ObjectIdSet to avoid wrapper Hoist ObjectIdSet up to lib as part of the public API and add the interface to some common types like PackIndex and JGit custom ObjectId map types. This cleans up wrapper code in a number of places by allowing direct use of the types as an ObjectIdSet. Future commits can now rely on ObjectIdSet as a simple read-only type to check a set of objects from a number of storage options. Change-Id: Ib62b062421d475bd52abd6c84a73916ef36e084b --- .../internal/storage/file/PackWriterTest.java | 15 +---- .../storage/dfs/DfsGarbageCollector.java | 19 ++---- .../storage/dfs/DfsPackCompactor.java | 15 ++--- .../jgit/internal/storage/file/GC.java | 15 +---- .../jgit/internal/storage/file/PackIndex.java | 9 ++- .../internal/storage/pack/PackWriter.java | 13 +--- .../eclipse/jgit/lib/ObjectIdOwnerMap.java | 4 +- .../src/org/eclipse/jgit/lib/ObjectIdSet.java | 64 +++++++++++++++++++ .../eclipse/jgit/lib/ObjectIdSubclassMap.java | 3 +- 9 files changed, 95 insertions(+), 62 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java index 80efe199f..3c44799b6 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java @@ -43,12 +43,12 @@ package org.eclipse.jgit.internal.storage.file; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -67,13 +67,12 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; import org.eclipse.jgit.internal.storage.pack.PackWriter; -import org.eclipse.jgit.internal.storage.pack.PackWriter.ObjectIdSet; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository.BranchBuilder; -import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; @@ -528,7 +527,7 @@ public void testExclude() throws Exception { RevCommit c2 = bb.commit().add("f", contentB).create(); testRepo.getRevWalk().parseHeaders(c2); PackIndex pf2 = writePack(repo, Collections.singleton(c2), - Collections.singleton(objectIdSet(pf1))); + Collections. singleton(pf1)); assertContent( pf2, Arrays.asList(c2.getId(), c2.getTree().getId(), @@ -733,12 +732,4 @@ public int compare(MutableEntry o1, MutableEntry o2) { assertEquals(objectsOrder[i++].toObjectId(), me.toObjectId()); } } - - private static ObjectIdSet objectIdSet(final PackIndex idx) { - return new ObjectIdSet() { - public boolean contains(AnyObjectId objectId) { - return idx.hasObject(objectId); - } - }; - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java index faf27e32b..bcc46c355 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java @@ -67,7 +67,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectIdOwnerMap; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevWalk; @@ -87,7 +87,7 @@ public class DfsGarbageCollector { private final List newPackStats; - private final List newPackObj; + private final List newPackObj; private DfsReader ctx; @@ -117,7 +117,7 @@ public DfsGarbageCollector(DfsRepository repository) { objdb = repo.getObjectDatabase(); newPackDesc = new ArrayList(4); newPackStats = new ArrayList(4); - newPackObj = new ArrayList(4); + newPackObj = new ArrayList(4); packConfig = new PackConfig(repo); packConfig.setIndexVersion(2); @@ -288,7 +288,7 @@ private void packRest(ProgressMonitor pm) throws IOException { return; try (PackWriter pw = newPackWriter()) { - for (PackWriter.ObjectIdSet packedObjs : newPackObj) + for (ObjectIdSet packedObjs : newPackObj) pw.excludeObjects(packedObjs); pw.preparePack(pm, nonHeads, allHeads); if (0 < pw.getObjectCount()) @@ -328,7 +328,7 @@ private void packGarbage(ProgressMonitor pm) throws IOException { } private boolean anyPackHas(AnyObjectId id) { - for (PackWriter.ObjectIdSet packedObjs : newPackObj) + for (ObjectIdSet packedObjs : newPackObj) if (packedObjs.contains(id)) return true; return false; @@ -389,17 +389,10 @@ private DfsPackDescription writePack(PackSource source, PackWriter pw, } } - final ObjectIdOwnerMap packedObjs = pw - .getObjectSet(); - newPackObj.add(new PackWriter.ObjectIdSet() { - public boolean contains(AnyObjectId objectId) { - return packedObjs.contains(objectId); - } - }); - PackStatistics stats = pw.getStatistics(); pack.setPackStats(stats); newPackStats.add(stats); + newPackObj.add(pw.getObjectSet()); DfsBlockCache.getInstance().getOrCreate(pack, null); return pack; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java index 7073763a7..11aef7fea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java @@ -62,6 +62,7 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.revwalk.RevFlag; import org.eclipse.jgit.revwalk.RevObject; @@ -91,7 +92,7 @@ public class DfsPackCompactor { private final List srcPacks; - private final List exclude; + private final List exclude; private final List newPacks; @@ -113,7 +114,7 @@ public DfsPackCompactor(DfsRepository repository) { repo = repository; autoAddSize = 5 * 1024 * 1024; // 5 MiB srcPacks = new ArrayList(); - exclude = new ArrayList(4); + exclude = new ArrayList(4); newPacks = new ArrayList(1); newStats = new ArrayList(1); } @@ -164,7 +165,7 @@ public DfsPackCompactor autoAdd() throws IOException { * objects to not include. * @return {@code this}. */ - public DfsPackCompactor exclude(PackWriter.ObjectIdSet set) { + public DfsPackCompactor exclude(ObjectIdSet set) { exclude.add(set); return this; } @@ -183,11 +184,7 @@ public DfsPackCompactor exclude(DfsPackFile pack) throws IOException { try (DfsReader ctx = (DfsReader) repo.newObjectReader()) { idx = pack.getPackIndex(ctx); } - return exclude(new PackWriter.ObjectIdSet() { - public boolean contains(AnyObjectId id) { - return idx.hasObject(id); - } - }); + return exclude(idx); } /** @@ -343,7 +340,7 @@ private List toInclude(DfsPackFile src, DfsReader ctx) RevObject obj = rw.lookupOrNull(id); if (obj != null && (obj.has(added) || obj.has(isBase))) continue; - for (PackWriter.ObjectIdSet e : exclude) + for (ObjectIdSet e : exclude) if (e.contains(id)) continue SCAN; want.add(new ObjectIdWithOffset(id, ent.getOffset())); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index c0f56f448..7b3966de1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -79,13 +79,12 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.internal.storage.pack.PackWriter; -import org.eclipse.jgit.internal.storage.pack.PackWriter.ObjectIdSet; -import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; @@ -552,7 +551,7 @@ public Collection repack() throws IOException { List excluded = new LinkedList(); for (final PackFile f : repo.getObjectDatabase().getPacks()) if (f.shouldBeKept()) - excluded.add(objectIdSet(f.getIndex())); + excluded.add(f.getIndex()); tagTargets.addAll(allHeads); nonHeads.addAll(indexObjects); @@ -564,7 +563,7 @@ public Collection repack() throws IOException { tagTargets, excluded); if (heads != null) { ret.add(heads); - excluded.add(0, objectIdSet(heads.getIndex())); + excluded.add(0, heads.getIndex()); } } if (!nonHeads.isEmpty()) { @@ -1000,12 +999,4 @@ public void setExpire(Date expire) { this.expire = expire; expireAgeMillis = -1; } - - private static ObjectIdSet objectIdSet(final PackIndex idx) { - return new ObjectIdSet() { - public boolean contains(AnyObjectId objectId) { - return idx.hasObject(objectId); - } - }; - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java index 0040aea71..f36bd4d70 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java @@ -60,6 +60,7 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.NB; @@ -72,7 +73,8 @@ * by ObjectId. *

*/ -public abstract class PackIndex implements Iterable { +public abstract class PackIndex + implements Iterable, ObjectIdSet { /** * Open an existing pack .idx file for reading. *

@@ -166,6 +168,11 @@ public boolean hasObject(final AnyObjectId id) { return findOffset(id) != -1; } + @Override + public boolean contains(AnyObjectId id) { + return findOffset(id) != -1; + } + /** * Provide iterator that gives access to index entries. Note, that iterator * returns reference to mutable object, the same reference in each call - diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java index f3f77c449..763f35c21 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java @@ -99,6 +99,7 @@ import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdOwnerMap; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.ProgressMonitor; @@ -161,18 +162,6 @@ public class PackWriter implements AutoCloseable { private static final int PACK_VERSION_GENERATED = 2; - /** A collection of object ids. */ - public interface ObjectIdSet { - /** - * Returns true if the objectId is contained within the collection. - * - * @param objectId - * the objectId to find - * @return whether the collection contains the objectId. - */ - boolean contains(AnyObjectId objectId); - } - private static final Map, Boolean> instances = new ConcurrentHashMap, Boolean>(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java index 95b16d917..442261cbd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java @@ -67,8 +67,8 @@ * @param * type of subclass of ObjectId that will be stored in the map. */ -public class ObjectIdOwnerMap implements - Iterable { +public class ObjectIdOwnerMap + implements Iterable, ObjectIdSet { /** Size of the initial directory, will grow as necessary. */ private static final int INITIAL_DIRECTORY = 1024; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java new file mode 100644 index 000000000..0b5848463 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +/** + * Simple set of ObjectIds. + *

+ * Usually backed by a read-only data structure such as + * {@link org.eclipse.jgit.internal.storage.file.PackIndex}. Mutable types like + * {@link ObjectIdOwnerMap} also implement the interface by checking keys. + * + * @since 4.2 + */ +public interface ObjectIdSet { + /** + * Returns true if the objectId is contained within the collection. + * + * @param objectId + * the objectId to find + * @return whether the collection contains the objectId. + */ + boolean contains(AnyObjectId objectId); +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java index 48aa109e7..faed64bfe 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java @@ -60,7 +60,8 @@ * @param * type of subclass of ObjectId that will be stored in the map. */ -public class ObjectIdSubclassMap implements Iterable { +public class ObjectIdSubclassMap + implements Iterable, ObjectIdSet { private static final int INITIAL_TABLE_SIZE = 2048; int size; From 018a91b7155e6edd107d9dc4909cdff166ef0fc1 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Tue, 29 Dec 2015 16:53:56 -0800 Subject: [PATCH 40/89] Unify fetch and receive ObjectChecker setup This avoids duplication of code between receive-pack and fetch-pack paths. Separate methods are still required to check use of receive.fsckobjects vs. fetch.fsckobjects, both of which default to transfer.fsckobjects. Change-Id: I41193e093e981a79fc2f63914e273aaa44b82162 --- .../jgit/transport/BaseReceivePack.java | 45 ++++--------------- .../jgit/transport/TransferConfig.java | 40 +++++++++++------ 2 files changed, 36 insertions(+), 49 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java index c224d8eeb..728643e92 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java @@ -293,18 +293,20 @@ protected BaseReceivePack(final Repository into) { db = into; walk = new RevWalk(db); - final ReceiveConfig cfg = db.getConfig().get(ReceiveConfig.KEY); - objectChecker = cfg.newObjectChecker(); - allowCreates = cfg.allowCreates; + TransferConfig tc = db.getConfig().get(TransferConfig.KEY); + objectChecker = tc.newReceiveObjectChecker(); + + ReceiveConfig rc = db.getConfig().get(ReceiveConfig.KEY); + allowCreates = rc.allowCreates; allowAnyDeletes = true; - allowBranchDeletes = cfg.allowDeletes; - allowNonFastForwards = cfg.allowNonFastForwards; - allowOfsDelta = cfg.allowOfsDelta; + allowBranchDeletes = rc.allowDeletes; + allowNonFastForwards = rc.allowNonFastForwards; + allowOfsDelta = rc.allowOfsDelta; advertiseRefsHook = AdvertiseRefsHook.DEFAULT; refFilter = RefFilter.DEFAULT; advertisedHaves = new HashSet(); clientShallowCommits = new HashSet(); - signedPushConfig = cfg.signedPush; + signedPushConfig = rc.signedPush; } /** Configuration for receive operations. */ @@ -315,32 +317,13 @@ public ReceiveConfig parse(final Config cfg) { } }; - final boolean checkReceivedObjects; - final boolean allowLeadingZeroFileMode; - final boolean allowInvalidPersonIdent; - final boolean safeForWindows; - final boolean safeForMacOS; - final boolean allowCreates; final boolean allowDeletes; final boolean allowNonFastForwards; final boolean allowOfsDelta; - final SignedPushConfig signedPush; ReceiveConfig(final Config config) { - checkReceivedObjects = config.getBoolean( - "receive", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$ - config.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$ - allowLeadingZeroFileMode = checkReceivedObjects - && config.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$ - allowInvalidPersonIdent = checkReceivedObjects - && config.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$ - safeForWindows = checkReceivedObjects - && config.getBoolean("fsck", "safeForWindows", false); //$NON-NLS-1$ //$NON-NLS-2$ - safeForMacOS = checkReceivedObjects - && config.getBoolean("fsck", "safeForMacOS", false); //$NON-NLS-1$ //$NON-NLS-2$ - allowCreates = true; allowDeletes = !config.getBoolean("receive", "denydeletes", false); //$NON-NLS-1$ //$NON-NLS-2$ allowNonFastForwards = !config.getBoolean("receive", //$NON-NLS-1$ @@ -349,16 +332,6 @@ public ReceiveConfig parse(final Config cfg) { true); signedPush = SignedPushConfig.KEY.parse(config); } - - ObjectChecker newObjectChecker() { - if (!checkReceivedObjects) - return null; - return new ObjectChecker() - .setAllowLeadingZeroFileMode(allowLeadingZeroFileMode) - .setAllowInvalidPersonIdent(allowInvalidPersonIdent) - .setSafeForWindows(safeForWindows) - .setSafeForMacOS(safeForMacOS); - } } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java index f0c513427..f9b74c84e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java @@ -46,6 +46,7 @@ import java.util.HashMap; import java.util.Map; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.ObjectChecker; @@ -65,7 +66,8 @@ public TransferConfig parse(final Config cfg) { } }; - private final boolean checkReceivedObjects; + private final boolean fetchFsck; + private final boolean receiveFsck; private final boolean allowLeadingZeroFileMode; private final boolean allowInvalidPersonIdent; private final boolean safeForWindows; @@ -79,18 +81,14 @@ public TransferConfig parse(final Config cfg) { } TransferConfig(final Config rc) { - checkReceivedObjects = rc.getBoolean( - "fetch", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$ - rc.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$ - allowLeadingZeroFileMode = checkReceivedObjects - && rc.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$ - allowInvalidPersonIdent = checkReceivedObjects - && rc.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$ - safeForWindows = checkReceivedObjects - && rc.getBoolean("fsck", "safeForWindows", //$NON-NLS-1$ //$NON-NLS-2$ + boolean fsck = rc.getBoolean("transfer", "fsckobjects", false); //$NON-NLS-1$ //$NON-NLS-2$ + fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ + receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ + allowLeadingZeroFileMode = rc.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$ + allowInvalidPersonIdent = rc.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$ + safeForWindows = rc.getBoolean("fsck", "safeForWindows", //$NON-NLS-1$ //$NON-NLS-2$ SystemReader.getInstance().isWindows()); - safeForMacOS = checkReceivedObjects - && rc.getBoolean("fsck", "safeForMacOS", //$NON-NLS-1$ //$NON-NLS-2$ + safeForMacOS = rc.getBoolean("fsck", "safeForMacOS", //$NON-NLS-1$ //$NON-NLS-2$ SystemReader.getInstance().isMacOS()); allowTipSha1InWant = rc.getBoolean( @@ -105,9 +103,25 @@ public TransferConfig parse(final Config cfg) { * enabled in the repository configuration. * @since 3.6 */ + @Nullable public ObjectChecker newObjectChecker() { - if (!checkReceivedObjects) + return newObjectChecker(fetchFsck); + } + + /** + * @return checker to verify objects pushed into this repository, or null if + * checking is not enabled in the repository configuration. + * @since 4.2 + */ + @Nullable + public ObjectChecker newReceiveObjectChecker() { + return newObjectChecker(receiveFsck); + } + + private ObjectChecker newObjectChecker(boolean check) { + if (!check) { return null; + } return new ObjectChecker() .setAllowLeadingZeroFileMode(allowLeadingZeroFileMode) .setAllowInvalidPersonIdent(allowInvalidPersonIdent) From e3acf017486204fb56c33c5edd51d5f2409be7ee Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 30 Dec 2015 12:23:06 -0800 Subject: [PATCH 41/89] ObjectChecker: use java.text.Normalizer directly Base Java version for JGit is now Java 7. The java.text.Normalizer class was available in Java 6. Reflection is no longer required to normalize strings for Mac OS X. Change-Id: I98e14b72629a7a729a2d40a3aa275932841274e8 --- .../org/eclipse/jgit/lib/ObjectChecker.java | 57 +------------------ 1 file changed, 2 insertions(+), 55 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java index a7a67a881..855d9d750 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java @@ -48,9 +48,8 @@ import static org.eclipse.jgit.util.RawParseUtils.nextLF; import static org.eclipse.jgit.util.RawParseUtils.parseBase10; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.text.MessageFormat; +import java.text.Normalizer; import java.util.HashSet; import java.util.Locale; import java.util.Set; @@ -790,58 +789,6 @@ public void checkBlob(final byte[] raw) throws CorruptObjectException { private String normalize(byte[] raw, int ptr, int end) { String n = RawParseUtils.decode(raw, ptr, end).toLowerCase(Locale.US); - return macosx ? Normalizer.normalize(n) : n; - } - - private static class Normalizer { - // TODO Simplify invocation to Normalizer after dropping Java 5. - private static final Method normalize; - private static final Object nfc; - static { - Method method; - Object formNfc; - try { - Class formClazz = Class.forName("java.text.Normalizer$Form"); //$NON-NLS-1$ - formNfc = formClazz.getField("NFC").get(null); //$NON-NLS-1$ - method = Class.forName("java.text.Normalizer") //$NON-NLS-1$ - .getMethod("normalize", CharSequence.class, formClazz); //$NON-NLS-1$ - } catch (ClassNotFoundException e) { - method = null; - formNfc = null; - } catch (NoSuchFieldException e) { - method = null; - formNfc = null; - } catch (NoSuchMethodException e) { - method = null; - formNfc = null; - } catch (SecurityException e) { - method = null; - formNfc = null; - } catch (IllegalArgumentException e) { - method = null; - formNfc = null; - } catch (IllegalAccessException e) { - method = null; - formNfc = null; - } - normalize = method; - nfc = formNfc; - } - - static String normalize(String in) { - if (normalize == null) - return in; - try { - return (String) normalize.invoke(null, in, nfc); - } catch (IllegalAccessException e) { - return in; - } catch (InvocationTargetException e) { - if (e.getCause() instanceof RuntimeException) - throw (RuntimeException) e.getCause(); - if (e.getCause() instanceof Error) - throw (Error) e.getCause(); - return in; - } - } + return macosx ? Normalizer.normalize(n, Normalizer.Form.NFC) : n; } } From 3fc93f8a562e96d354546ad6ff8c9dd56709c5e5 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Thu, 23 Apr 2015 02:11:00 +0200 Subject: [PATCH 42/89] Rename files using NIO2 atomic rename Bug: 319233 Change-Id: I5137212f5cd3195a52f90ed5e4ce3cf194a13efd Signed-off-by: Matthias Sohn --- .../org/eclipse/jgit/api/ApplyCommand.java | 9 ++- .../eclipse/jgit/api/StashDropCommand.java | 11 +-- .../jgit/dircache/DirCacheCheckout.java | 10 +-- .../jgit/internal/storage/file/GC.java | 51 ++++++-------- .../jgit/internal/storage/file/LockFile.java | 70 ++++++------------- .../storage/file/ObjectDirectory.java | 19 ++++- .../file/ObjectDirectoryPackParser.java | 14 ++-- .../storage/file/RefDirectoryRename.java | 23 +++++- 8 files changed, 111 insertions(+), 96 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java index 6a945e4d3..676ae0300 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java @@ -47,6 +47,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; @@ -141,9 +142,13 @@ public ApplyResult call() throws GitAPIException, PatchFormatException, case RENAME: f = getFile(fh.getOldPath(), false); File dest = getFile(fh.getNewPath(), false); - if (!f.renameTo(dest)) + try { + FileUtils.rename(f, dest, + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { throw new PatchApplyException(MessageFormat.format( - JGitText.get().renameFileFailed, f, dest)); + JGitText.get().renameFileFailed, f, dest), e); + } break; case COPY: f = getFile(fh.getOldPath(), false); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java index 7923fd49b..f6903be05 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java @@ -46,6 +46,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.util.List; @@ -220,12 +221,14 @@ public ObjectId call() throws GitAPIException { entry.getWho(), entry.getComment()); entryId = entry.getNewId(); } - if (!stashLockFile.renameTo(stashFile)) { - FileUtils.delete(stashFile); - if (!stashLockFile.renameTo(stashFile)) + try { + FileUtils.rename(stashLockFile, stashFile, + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { throw new JGitInternalException(MessageFormat.format( JGitText.get().renameFileFailed, - stashLockFile.getPath(), stashFile.getPath())); + stashLockFile.getPath(), stashFile.getPath()), + e); } } catch (IOException e) { throw new JGitInternalException(JGitText.get().stashDropFailed, e); 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 4eb688170..a1e1d15ac 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -46,6 +46,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; @@ -1319,11 +1320,12 @@ public static void checkoutEntry(Repository repo, DirCacheEntry entry, if (deleteRecursive && f.isDirectory()) { FileUtils.delete(f, FileUtils.RECURSIVE); } - FileUtils.rename(tmpFile, f); + FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE); } catch (IOException e) { - throw new IOException(MessageFormat.format( - JGitText.get().renameFileFailed, tmpFile.getPath(), - f.getPath())); + throw new IOException( + MessageFormat.format(JGitText.get().renameFileFailed, + tmpFile.getPath(), f.getPath()), + e); } finally { if (tmpFile.exists()) { FileUtils.delete(tmpFile); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index 7b3966de1..a5c95b3bc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -53,6 +53,7 @@ import java.io.OutputStream; import java.nio.channels.Channels; import java.nio.channels.FileChannel; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.text.ParseException; import java.util.ArrayList; @@ -789,39 +790,33 @@ public int compare(PackExt o1, PackExt o2) { break; } tmpPack.setReadOnly(); - boolean delete = true; - try { - FileUtils.rename(tmpPack, realPack); - delete = false; - for (Map.Entry tmpEntry : tmpExts.entrySet()) { - File tmpExt = tmpEntry.getValue(); - tmpExt.setReadOnly(); - File realExt = nameFor( - id, "." + tmpEntry.getKey().getExtension()); //$NON-NLS-1$ + FileUtils.rename(tmpPack, realPack, StandardCopyOption.ATOMIC_MOVE); + for (Map.Entry tmpEntry : tmpExts.entrySet()) { + File tmpExt = tmpEntry.getValue(); + tmpExt.setReadOnly(); + + File realExt = nameFor(id, + "." + tmpEntry.getKey().getExtension()); //$NON-NLS-1$ + try { + FileUtils.rename(tmpExt, realExt, + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { + File newExt = new File(realExt.getParentFile(), + realExt.getName() + ".new"); //$NON-NLS-1$ try { - FileUtils.rename(tmpExt, realExt); - } catch (IOException e) { - File newExt = new File(realExt.getParentFile(), - realExt.getName() + ".new"); //$NON-NLS-1$ - if (!tmpExt.renameTo(newExt)) - newExt = tmpExt; - throw new IOException(MessageFormat.format( - JGitText.get().panicCantRenameIndexFile, newExt, - realExt)); - } - } - - } finally { - if (delete) { - if (tmpPack.exists()) - tmpPack.delete(); - for (File tmpExt : tmpExts.values()) { - if (tmpExt.exists()) - tmpExt.delete(); + FileUtils.rename(tmpExt, newExt, + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e2) { + newExt = tmpExt; + e = e2; } + throw new IOException(MessageFormat.format( + JGitText.get().panicCantRenameIndexFile, newExt, + realExt), e); } } + return repo.getObjectDatabase().openPack(realPack); } finally { if (tmpPack != null && tmpPack.exists()) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java index e23ca741b..ce9677a62 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java @@ -54,6 +54,7 @@ import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import org.eclipse.jgit.errors.LockFailedException; @@ -128,8 +129,6 @@ public boolean accept(File dir, String name) { private FileSnapshot commitSnapshot; - private final FS fs; - /** * Create a new lock for any file. * @@ -138,11 +137,24 @@ public boolean accept(File dir, String name) { * @param fs * the file system abstraction which will be necessary to perform * certain file system operations. + * @deprecated use {@link LockFile#LockFile(File)} instead */ + @Deprecated public LockFile(final File f, final FS fs) { ref = f; lck = getLockFile(ref); - this.fs = fs; + } + + /** + * Create a new lock for any file. + * + * @param f + * the file that will be locked. + * @since 4.2 + */ + public LockFile(final File f) { + ref = f; + lck = getLockFile(ref); } /** @@ -441,56 +453,14 @@ public boolean commit() { } saveStatInformation(); - if (lck.renameTo(ref)) { + try { + FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE); haveLck = false; return true; + } catch (IOException e) { + unlock(); + return false; } - if (!ref.exists() || deleteRef()) { - if (renameLock()) { - haveLck = false; - return true; - } - } - unlock(); - return false; - } - - private boolean deleteRef() { - if (!fs.retryFailedLockFileCommit()) - return ref.delete(); - - // File deletion fails on windows if another thread is - // concurrently reading the same file. So try a few times. - // - for (int attempts = 0; attempts < 10; attempts++) { - if (ref.delete()) - return true; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - return false; - } - } - return false; - } - - private boolean renameLock() { - if (!fs.retryFailedLockFileCommit()) - return lck.renameTo(ref); - - // File renaming fails on windows if another thread is - // concurrently reading the same file. So try a few times. - // - for (int attempts = 0; attempts < 10; attempts++) { - if (lck.renameTo(ref)) - return true; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - return false; - } - } - return false; } private void saveStatInformation() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java index bd1d488d9..ea8052851 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java @@ -52,6 +52,9 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -608,10 +611,16 @@ InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId id, FileUtils.delete(tmp, FileUtils.RETRY); return InsertLooseObjectResult.EXISTS_LOOSE; } - if (tmp.renameTo(dst)) { + try { + Files.move(tmp.toPath(), dst.toPath(), + StandardCopyOption.ATOMIC_MOVE); dst.setReadOnly(); unpackedObjectCache.add(id); return InsertLooseObjectResult.INSERTED; + } catch (AtomicMoveNotSupportedException e) { + LOG.error(e.getMessage(), e); + } catch (IOException e) { + // ignore } // Maybe the directory doesn't exist yet as the object @@ -619,10 +628,16 @@ InsertLooseObjectResult insertUnpackedObject(File tmp, ObjectId id, // try the rename first as the directory likely does exist. // FileUtils.mkdir(dst.getParentFile(), true); - if (tmp.renameTo(dst)) { + try { + Files.move(tmp.toPath(), dst.toPath(), + StandardCopyOption.ATOMIC_MOVE); dst.setReadOnly(); unpackedObjectCache.add(id); return InsertLooseObjectResult.INSERTED; + } catch (AtomicMoveNotSupportedException e) { + LOG.error(e.getMessage(), e); + } catch (IOException e) { + LOG.debug(e.getMessage(), e); } if (!createDuplicate && has(id)) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java index 1c076ee09..2e6c245ea 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java @@ -50,6 +50,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; +import java.nio.file.StandardCopyOption; import java.security.MessageDigest; import java.text.MessageFormat; import java.util.Arrays; @@ -476,20 +477,25 @@ private PackLock renameAndOpenPack(final String lockMessage) } } - if (!tmpPack.renameTo(finalPack)) { + try { + FileUtils.rename(tmpPack, finalPack, + StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { cleanupTemporaryFiles(); keep.unlock(); throw new IOException(MessageFormat.format( - JGitText.get().cannotMovePackTo, finalPack)); + JGitText.get().cannotMovePackTo, finalPack), e); } - if (!tmpIdx.renameTo(finalIdx)) { + try { + FileUtils.rename(tmpIdx, finalIdx, StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { cleanupTemporaryFiles(); keep.unlock(); if (!finalPack.delete()) finalPack.deleteOnExit(); throw new IOException(MessageFormat.format( - JGitText.get().cannotMoveIndexTo, finalIdx)); + JGitText.get().cannotMoveIndexTo, finalIdx), e); } try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java index ba4a63d7f..4b803a514 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java @@ -46,6 +46,8 @@ import java.io.File; import java.io.IOException; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.StandardCopyOption; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -54,6 +56,8 @@ import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.util.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Rename any reference stored by {@link RefDirectory}. @@ -66,6 +70,9 @@ * directory that happens to match the source name. */ class RefDirectoryRename extends RefRename { + private static final Logger LOG = LoggerFactory + .getLogger(RefDirectoryRename.class); + private final RefDirectory refdb; /** @@ -201,13 +208,25 @@ private boolean renameLog(RefUpdate src, RefUpdate dst) { } private static boolean rename(File src, File dst) { - if (src.renameTo(dst)) + try { + FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE); return true; + } catch (AtomicMoveNotSupportedException e) { + LOG.error(e.getMessage(), e); + } catch (IOException e) { + // ignore + } File dir = dst.getParentFile(); if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory()) return false; - return src.renameTo(dst); + try { + FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE); + return true; + } catch (IOException e) { + LOG.error(e.getMessage(), e); + return false; + } } private boolean linkHEAD(RefUpdate target) { From fa7ce0e0f3a8973667b0d51966fc9bcb4fdbe505 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Tue, 29 Dec 2015 15:52:16 -0800 Subject: [PATCH 43/89] ObjectChecker: allow some objects to skip errors Some ancient objects may be broken, but in a relatively harmless way. Allow the ObjectChecker caller to whitelist specific objects that are going to fail checks, but that have been reviewed by a human and decided the objects are OK enough to permit continued use of. This avoids needing to rewrite history to scrub the broken objects out. Honor the git-core fsck.skipList configuration setting when receiving a push or fetching from a remote repository. Change-Id: I62bd7c0b0848981f73dd7c752860fd02794233a6 --- .../eclipse/jgit/junit/TestRepository.java | 4 +- .../eclipse/jgit/lib/ObjectCheckerTest.java | 1345 ++++++----------- .../storage/file/LazyObjectIdSetFile.java | 106 ++ .../org/eclipse/jgit/lib/ObjectChecker.java | 216 ++- .../eclipse/jgit/transport/PackParser.java | 2 +- .../jgit/transport/TransferConfig.java | 15 +- .../jgit/transport/WalkFetchConnection.java | 7 +- 7 files changed, 765 insertions(+), 930 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index ac9685d37..c941afc78 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -822,7 +822,7 @@ public void fsck(RevObject... tips) throws MissingObjectException, break; final byte[] bin = db.open(o, o.getType()).getCachedBytes(); - oc.checkCommit(bin); + oc.checkCommit(o, bin); assertHash(o, bin); } @@ -832,7 +832,7 @@ public void fsck(RevObject... tips) throws MissingObjectException, break; final byte[] bin = db.open(o, o.getType()).getCachedBytes(); - oc.check(o.getType(), bin); + oc.check(o, o.getType(), bin); assertHash(o, bin); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java index 3abe81cf8..951930309 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java @@ -45,7 +45,14 @@ package org.eclipse.jgit.lib; import static java.lang.Integer.valueOf; -import static java.lang.Long.valueOf; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Constants.OBJ_BAD; +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_TAG; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; +import static org.eclipse.jgit.lib.Constants.encode; +import static org.eclipse.jgit.lib.Constants.encodeASCII; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -67,15 +74,10 @@ public void setUp() throws Exception { @Test public void testInvalidType() { - try { - checker.check(Constants.OBJ_BAD, new byte[0]); - fail("Did not throw CorruptObjectException"); - } catch (CorruptObjectException e) { - final String m = e.getMessage(); - assertEquals(MessageFormat.format( - JGitText.get().corruptObjectInvalidType2, - valueOf(Constants.OBJ_BAD)), m); - } + String msg = MessageFormat.format( + JGitText.get().corruptObjectInvalidType2, + valueOf(OBJ_BAD)); + assertCorrupt(msg, OBJ_BAD, new byte[0]); } @Test @@ -84,13 +86,13 @@ public void testCheckBlob() throws CorruptObjectException { checker.checkBlob(new byte[0]); checker.checkBlob(new byte[1]); - checker.check(Constants.OBJ_BLOB, new byte[0]); - checker.check(Constants.OBJ_BLOB, new byte[1]); + checker.check(OBJ_BLOB, new byte[0]); + checker.check(OBJ_BLOB, new byte[1]); } @Test public void testValidCommitNoParent() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -99,14 +101,14 @@ public void testValidCommitNoParent() throws CorruptObjectException { b.append("author A. U. Thor 1 +0000\n"); b.append("committer A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test public void testValidCommitBlankAuthor() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -115,9 +117,9 @@ public void testValidCommitBlankAuthor() throws CorruptObjectException { b.append("author <> 0 +0000\n"); b.append("committer <> 0 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test @@ -127,15 +129,13 @@ public void testCommitCorruptAuthor() throws CorruptObjectException { b.append("author b 0 +0000\n"); b.append("committer <> 0 +0000\n"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid author", OBJ_COMMIT, data); checker.setAllowInvalidPersonIdent(true); checker.checkCommit(data); + + checker.setAllowInvalidPersonIdent(false); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test @@ -145,20 +145,18 @@ public void testCommitCorruptCommitter() throws CorruptObjectException { b.append("author <> 0 +0000\n"); b.append("committer b 0 +0000\n"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid committer", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid committer", OBJ_COMMIT, data); checker.setAllowInvalidPersonIdent(true); checker.checkCommit(data); + + checker.setAllowInvalidPersonIdent(false); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test public void testValidCommit1Parent() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -171,14 +169,14 @@ public void testValidCommit1Parent() throws CorruptObjectException { b.append("author A. U. Thor 1 +0000\n"); b.append("committer A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test public void testValidCommit2Parent() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -195,14 +193,14 @@ public void testValidCommit2Parent() throws CorruptObjectException { b.append("author A. U. Thor 1 +0000\n"); b.append("committer A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test public void testValidCommit128Parent() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -217,15 +215,15 @@ public void testValidCommit128Parent() throws CorruptObjectException { b.append("author A. U. Thor 1 +0000\n"); b.append("committer A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test public void testValidCommitNormalTime() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); - final String when = "1222757360 -0730"; + StringBuilder b = new StringBuilder(); + String when = "1222757360 -0730"; b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); @@ -234,844 +232,530 @@ public void testValidCommitNormalTime() throws CorruptObjectException { b.append("author A. U. Thor " + when + "\n"); b.append("committer A. U. Thor " + when + "\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkCommit(data); - checker.check(Constants.OBJ_COMMIT, data); + checker.check(OBJ_COMMIT, data); } @Test public void testInvalidCommitNoTree1() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("parent "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("no tree header", e.getMessage()); - } + assertCorrupt("no tree header", OBJ_COMMIT, b); } @Test public void testInvalidCommitNoTree2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("trie "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("no tree header", e.getMessage()); - } + assertCorrupt("no tree header", OBJ_COMMIT, b); } @Test public void testInvalidCommitNoTree3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree"); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("no tree header", e.getMessage()); - } + assertCorrupt("no tree header", OBJ_COMMIT, b); } @Test public void testInvalidCommitNoTree4() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree\t"); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("no tree header", e.getMessage()); - } + assertCorrupt("no tree header", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidTree1() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("zzzzfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid tree", e.getMessage()); - } + assertCorrupt("invalid tree", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidTree2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append("z\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid tree", e.getMessage()); - } + assertCorrupt("invalid tree", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidTree3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9b"); b.append("\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid tree", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid tree", OBJ_COMMIT, data); + assertSkipListRejects("invalid tree", OBJ_COMMIT, data); } @Test public void testInvalidCommitInvalidTree4() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid tree", e.getMessage()); - } + assertCorrupt("invalid tree", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidParent1() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("parent "); b.append("\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid parent", e.getMessage()); - } + assertCorrupt("invalid parent", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidParent2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("parent "); b.append("zzzzfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append("\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid parent", e.getMessage()); - } + assertCorrupt("invalid parent", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidParent3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("parent "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append("\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid parent", e.getMessage()); - } + assertCorrupt("invalid parent", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidParent4() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("parent "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append("z\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - assertEquals("invalid parent", e.getMessage()); - } + assertCorrupt("invalid parent", OBJ_COMMIT, b); } @Test public void testInvalidCommitInvalidParent5() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("parent\t"); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append("\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("no author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertCorrupt("no author", OBJ_COMMIT, data); } @Test - public void testInvalidCommitNoAuthor() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitNoAuthor() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("committer A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("no author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("no author", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitNoCommitter1() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitNoCommitter1() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("no committer", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("no committer", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitNoCommitter2() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitNoCommitter2() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author A. U. Thor 1 +0000\n"); b.append("\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("no committer", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("no committer", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor1() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor1() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid author", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor3() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor3() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid author", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor4() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor4() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author a +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid author", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor5() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor5() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author a \n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid author", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor6() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor6() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author a z"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid author", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidAuthor7() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidAuthor7() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author a 1 z"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid author", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid author", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test - public void testInvalidCommitInvalidCommitter() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidCommitInvalidCommitter() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("tree "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("author a 1 +0000\n"); b.append("committer a <"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkCommit(data); - fail("Did not catch corrupt object"); - } catch (CorruptObjectException e) { - // Yes, really, we complain about author not being - // found as the invalid parent line wasn't consumed. - assertEquals("invalid committer", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid committer", OBJ_COMMIT, data); + assertSkipListAccepts(OBJ_COMMIT, data); } @Test public void testValidTag() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tag test-tag\n"); b.append("tagger A. U. Thor 1 +0000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTag(data); - checker.check(Constants.OBJ_TAG, data); + checker.check(OBJ_TAG, data); } @Test public void testInvalidTagNoObject1() { - final StringBuilder b = new StringBuilder(); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no object header", e.getMessage()); - } + assertCorrupt("no object header", OBJ_TAG, new byte[0]); } @Test public void testInvalidTagNoObject2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object\t"); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no object header", e.getMessage()); - } + assertCorrupt("no object header", OBJ_TAG, b); } @Test public void testInvalidTagNoObject3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("obejct "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no object header", e.getMessage()); - } + assertCorrupt("no object header", OBJ_TAG, b); } @Test public void testInvalidTagNoObject4() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("zz9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("invalid object", e.getMessage()); - } + assertCorrupt("invalid object", OBJ_TAG, b); } @Test public void testInvalidTagNoObject5() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append(" \n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("invalid object", e.getMessage()); - } + assertCorrupt("invalid object", OBJ_TAG, b); } @Test public void testInvalidTagNoObject6() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("invalid object", e.getMessage()); - } + assertCorrupt("invalid object", OBJ_TAG, b); } @Test public void testInvalidTagNoType1() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no type header", e.getMessage()); - } + assertCorrupt("no type header", OBJ_TAG, b); } @Test public void testInvalidTagNoType2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type\tcommit\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no type header", e.getMessage()); - } + assertCorrupt("no type header", OBJ_TAG, b); } @Test public void testInvalidTagNoType3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("tpye commit\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no type header", e.getMessage()); - } + assertCorrupt("no type header", OBJ_TAG, b); } @Test public void testInvalidTagNoType4() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no tag header", e.getMessage()); - } + assertCorrupt("no tag header", OBJ_TAG, b); } @Test public void testInvalidTagNoTagHeader1() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no tag header", e.getMessage()); - } + assertCorrupt("no tag header", OBJ_TAG, b); } @Test public void testInvalidTagNoTagHeader2() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tag\tfoo\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no tag header", e.getMessage()); - } + assertCorrupt("no tag header", OBJ_TAG, b); } @Test public void testInvalidTagNoTagHeader3() { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tga foo\n"); - - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("no tag header", e.getMessage()); - } + assertCorrupt("no tag header", OBJ_TAG, b); } @Test public void testValidTagHasNoTaggerHeader() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tag foo\n"); - - checker.checkTag(Constants.encodeASCII(b.toString())); + checker.checkTag(encodeASCII(b.toString())); } @Test public void testInvalidTagInvalidTaggerHeader1() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); - + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tag foo\n"); b.append("tagger \n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("invalid tagger", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid tagger", OBJ_TAG, data); checker.setAllowInvalidPersonIdent(true); checker.checkTag(data); + + checker.setAllowInvalidPersonIdent(false); + assertSkipListAccepts(OBJ_TAG, data); } @Test - public void testInvalidTagInvalidTaggerHeader3() { - final StringBuilder b = new StringBuilder(); - + public void testInvalidTagInvalidTaggerHeader3() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); b.append("object "); b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); b.append('\n'); - b.append("type commit\n"); b.append("tag foo\n"); b.append("tagger a < 1 +000\n"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTag(data); - fail("incorrectly accepted invalid tag"); - } catch (CorruptObjectException e) { - assertEquals("invalid tagger", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid tagger", OBJ_TAG, data); + assertSkipListAccepts(OBJ_TAG, data); } @Test public void testValidEmptyTree() throws CorruptObjectException { checker.checkTree(new byte[0]); - checker.check(Constants.OBJ_TREE, new byte[0]); + checker.check(OBJ_TREE, new byte[0]); } @Test public void testValidTree1() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 regular-file"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTree2() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100755 executable"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTree3() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "40000 tree"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTree4() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "120000 symlink"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTree5() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "160000 git link"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTree6() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 .a"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test @@ -1084,314 +768,248 @@ public void testValidPosixTree() throws CorruptObjectException { @Test public void testValidTreeSorting1() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 fooaaa"); entry(b, "100755 foobar"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting2() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100755 fooaaa"); entry(b, "100644 foobar"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting3() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "40000 a"); entry(b, "100644 b"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting4() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "40000 b"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting5() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 a.c"); entry(b, "40000 a"); entry(b, "100644 a0c"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting6() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "40000 a"); entry(b, "100644 apple"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting7() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "40000 an orang"); entry(b, "40000 an orange"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testValidTreeSorting8() throws CorruptObjectException { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "100644 a0c"); entry(b, "100644 b"); - final byte[] data = Constants.encodeASCII(b.toString()); - checker.checkTree(data); + checker.checkTree(encodeASCII(b.toString())); } @Test public void testAcceptTreeModeWithZero() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "040000 a"); + byte[] data = encodeASCII(b.toString()); checker.setAllowLeadingZeroFileMode(true); - checker.checkTree(Constants.encodeASCII(b.toString())); + checker.checkTree(data); + + checker.setAllowLeadingZeroFileMode(false); + assertSkipListAccepts(OBJ_TREE, data); } @Test public void testInvalidTreeModeStartsWithZero1() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "0 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("mode starts with '0'", e.getMessage()); - } + assertCorrupt("mode starts with '0'", OBJ_TREE, b); } @Test public void testInvalidTreeModeStartsWithZero2() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "0100644 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("mode starts with '0'", e.getMessage()); - } + assertCorrupt("mode starts with '0'", OBJ_TREE, b); } @Test public void testInvalidTreeModeStartsWithZero3() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "040000 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("mode starts with '0'", e.getMessage()); - } + assertCorrupt("mode starts with '0'", OBJ_TREE, b); } @Test public void testInvalidTreeModeNotOctal1() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "8 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid mode character", e.getMessage()); - } + assertCorrupt("invalid mode character", OBJ_TREE, b); } @Test public void testInvalidTreeModeNotOctal2() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "Z a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid mode character", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid mode character", OBJ_TREE, data); + assertSkipListRejects("invalid mode character", OBJ_TREE, data); } @Test public void testInvalidTreeModeNotSupportedMode1() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "1 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid mode 1", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid mode 1", OBJ_TREE, data); + assertSkipListRejects("invalid mode 1", OBJ_TREE, data); } @Test public void testInvalidTreeModeNotSupportedMode2() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "170000 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid mode " + 0170000, e.getMessage()); - } + assertCorrupt("invalid mode " + 0170000, OBJ_TREE, b); } @Test public void testInvalidTreeModeMissingName() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("100644"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("truncated in mode", e.getMessage()); - } + assertCorrupt("truncated in mode", OBJ_TREE, b); } @Test public void testInvalidTreeNameContainsSlash() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 a/b"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("name contains '/'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("name contains '/'", OBJ_TREE, data); + assertSkipListRejects("name contains '/'", OBJ_TREE, data); } @Test public void testInvalidTreeNameIsEmpty() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 "); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("zero length name", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("zero length name", OBJ_TREE, data); + assertSkipListRejects("zero length name", OBJ_TREE, data); } @Test public void testInvalidTreeNameIsDot() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 ."); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.'", OBJ_TREE, data); + assertSkipListRejects("invalid name '.'", OBJ_TREE, data); } @Test public void testInvalidTreeNameIsDotDot() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); entry(b, "100644 .."); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '..'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '..'", OBJ_TREE, data); + assertSkipListRejects("invalid name '..'", OBJ_TREE, data); } @Test - public void testInvalidTreeNameIsGit() { + public void testInvalidTreeNameIsGit() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.git'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.git'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeNameIsMixedCaseGit() { + public void testInvalidTreeNameIsMixedCaseGit() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .GiT"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.GiT'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.GiT'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeNameIsMacHFSGit() { + public void testInvalidTreeNameIsMacHFSGit() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .gi\u200Ct"); - byte[] data = Constants.encode(b.toString()); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals( - "invalid name '.gi\u200Ct' contains ignorable Unicode characters", - e.getMessage()); - } + byte[] data = encode(b.toString()); + + // Fine on POSIX. + checker.checkTree(data); + + // Rejected on Mac OS. + checker.setSafeForMacOS(true); + assertCorrupt( + "invalid name '.gi\u200Ct' contains ignorable Unicode characters", + OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeNameIsMacHFSGit2() { + public void testInvalidTreeNameIsMacHFSGit2() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 \u206B.git"); - byte[] data = Constants.encode(b.toString()); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals( - "invalid name '\u206B.git' contains ignorable Unicode characters", - e.getMessage()); - } + byte[] data = encode(b.toString()); + + // Fine on POSIX. + checker.checkTree(data); + + // Rejected on Mac OS. + checker.setSafeForMacOS(true); + assertCorrupt( + "invalid name '\u206B.git' contains ignorable Unicode characters", + OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeNameIsMacHFSGit3() { + public void testInvalidTreeNameIsMacHFSGit3() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git\uFEFF"); - byte[] data = Constants.encode(b.toString()); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals( - "invalid name '.git\uFEFF' contains ignorable Unicode characters", - e.getMessage()); - } + byte[] data = encode(b.toString()); + + // Fine on POSIX. + checker.checkTree(data); + + // Rejected on Mac OS. + checker.setSafeForMacOS(true); + assertCorrupt( + "invalid name '.git\uFEFF' contains ignorable Unicode characters", + OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } private static byte[] concat(byte[] b1, byte[] b2) { @@ -1402,39 +1020,44 @@ private static byte[] concat(byte[] b1, byte[] b2) { } @Test - public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd() { - byte[] data = concat(Constants.encode("100644 .git"), + public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd() + throws CorruptObjectException { + byte[] data = concat(encode("100644 .git"), new byte[] { (byte) 0xef }); StringBuilder b = new StringBuilder(); entry(b, ""); - data = concat(data, Constants.encode(b.toString())); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals( - "invalid name contains byte sequence '0xef' which is not a valid UTF-8 character", - e.getMessage()); - } + data = concat(data, encode(b.toString())); + + // Fine on POSIX. + checker.checkTree(data); + + // Rejected on Mac OS. + checker.setSafeForMacOS(true); + assertCorrupt( + "invalid name contains byte sequence '0xef' which is not a valid UTF-8 character", + OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd2() { - byte[] data = concat(Constants.encode("100644 .git"), new byte[] { + public void testInvalidTreeNameIsMacHFSGitCorruptUTF8AtEnd2() + throws CorruptObjectException { + byte[] data = concat(encode("100644 .git"), + new byte[] { (byte) 0xe2, (byte) 0xab }); StringBuilder b = new StringBuilder(); entry(b, ""); - data = concat(data, Constants.encode(b.toString())); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals( - "invalid name contains byte sequence '0xe2ab' which is not a valid UTF-8 character", - e.getMessage()); - } + data = concat(data, encode(b.toString())); + + // Fine on POSIX. + checker.checkTree(data); + + // Rejected on Mac OS. + checker.setSafeForMacOS(true); + assertCorrupt( + "invalid name contains byte sequence '0xe2ab' which is not a valid UTF-8 character", + OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test @@ -1442,7 +1065,7 @@ public void testInvalidTreeNameIsNotMacHFSGit() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git\u200Cx"); - byte[] data = Constants.encode(b.toString()); + byte[] data = encode(b.toString()); checker.setSafeForMacOS(true); checker.checkTree(data); } @@ -1452,7 +1075,7 @@ public void testInvalidTreeNameIsNotMacHFSGit2() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .kit\u200C"); - byte[] data = Constants.encode(b.toString()); + byte[] data = encode(b.toString()); checker.setSafeForMacOS(true); checker.checkTree(data); } @@ -1462,21 +1085,17 @@ public void testInvalidTreeNameIsNotMacHFSGitOtherPlatform() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git\u200C"); - byte[] data = Constants.encode(b.toString()); + byte[] data = encode(b.toString()); checker.checkTree(data); } @Test - public void testInvalidTreeNameIsDotGitDot() { + public void testInvalidTreeNameIsDotGitDot() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git."); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.git.'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.git.'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test @@ -1484,20 +1103,17 @@ public void testValidTreeNameIsDotGitDotDot() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git.."); - checker.checkTree(Constants.encodeASCII(b.toString())); + checker.checkTree(encodeASCII(b.toString())); } @Test - public void testInvalidTreeNameIsDotGitSpace() { + public void testInvalidTreeNameIsDotGitSpace() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git "); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.git '", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.git '", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test @@ -1505,7 +1121,7 @@ public void testInvalidTreeNameIsDotGitSomething() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .gitfoobar"); - byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTree(data); } @@ -1514,7 +1130,7 @@ public void testInvalidTreeNameIsDotGitSomethingSpaceSomething() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .gitfoo bar"); - byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTree(data); } @@ -1523,7 +1139,7 @@ public void testInvalidTreeNameIsDotGitSomethingDot() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .gitfoobar."); - byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTree(data); } @@ -1532,251 +1148,182 @@ public void testInvalidTreeNameIsDotGitSomethingDotDot() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .gitfoobar.."); - byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTree(data); } @Test - public void testInvalidTreeNameIsDotGitDotSpace() { + public void testInvalidTreeNameIsDotGitDotSpace() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git. "); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.git. '", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.git. '", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeNameIsDotGitSpaceDot() { + public void testInvalidTreeNameIsDotGitSpaceDot() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .git . "); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name '.git . '", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name '.git . '", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeNameIsGITTilde1() { + public void testInvalidTreeNameIsGITTilde1() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 GIT~1"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name 'GIT~1'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name 'GIT~1'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeNameIsGiTTilde1() { + public void testInvalidTreeNameIsGiTTilde1() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 GiT~1"); - byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("invalid name 'GiT~1'", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("invalid name 'GiT~1'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test public void testValidTreeNameIsGitTilde11() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 GIT~11"); - byte[] data = Constants.encodeASCII(b.toString()); + byte[] data = encodeASCII(b.toString()); checker.checkTree(data); } @Test public void testInvalidTreeTruncatedInName() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("100644 b"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("truncated in name", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("truncated in name", OBJ_TREE, data); + assertSkipListRejects("truncated in name", OBJ_TREE, data); } @Test public void testInvalidTreeTruncatedInObjectId() { - final StringBuilder b = new StringBuilder(); + StringBuilder b = new StringBuilder(); b.append("100644 b\0\1\2"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("truncated in object id", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("truncated in object id", OBJ_TREE, data); + assertSkipListRejects("truncated in object id", OBJ_TREE, data); } @Test - public void testInvalidTreeBadSorting1() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeBadSorting1() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 foobar"); entry(b, "100644 fooaaa"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("incorrectly sorted", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("incorrectly sorted", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeBadSorting2() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeBadSorting2() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "40000 a"); entry(b, "100644 a.c"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("incorrectly sorted", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("incorrectly sorted", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeBadSorting3() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeBadSorting3() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 a0c"); entry(b, "40000 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("incorrectly sorted", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("incorrectly sorted", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeDuplicateNames1() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeDuplicateNames1() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "100644 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeDuplicateNames2() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeDuplicateNames2() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "100755 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeDuplicateNames3() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeDuplicateNames3() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "40000 a"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test - public void testInvalidTreeDuplicateNames4() { - final StringBuilder b = new StringBuilder(); + public void testInvalidTreeDuplicateNames4() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "100644 a.c"); entry(b, "100644 a.d"); entry(b, "100644 a.e"); entry(b, "40000 a"); entry(b, "100644 zoo"); - final byte[] data = Constants.encodeASCII(b.toString()); - try { - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + byte[] data = encodeASCII(b.toString()); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test public void testInvalidTreeDuplicateNames5() - throws UnsupportedEncodingException { + throws UnsupportedEncodingException, CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "100644 A"); byte[] data = b.toString().getBytes("UTF-8"); - try { - checker.setSafeForWindows(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + checker.setSafeForWindows(true); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test public void testInvalidTreeDuplicateNames6() - throws UnsupportedEncodingException { + throws UnsupportedEncodingException, CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "100644 A"); byte[] data = b.toString().getBytes("UTF-8"); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + checker.setSafeForMacOS(true); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test public void testInvalidTreeDuplicateNames7() - throws UnsupportedEncodingException { - try { - Class.forName("java.text.Normalizer"); - } catch (ClassNotFoundException e) { - // Ignore this test on Java 5 platform. - return; - } - + throws UnsupportedEncodingException, CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 \u0065\u0301"); entry(b, "100644 \u00e9"); byte[] data = b.toString().getBytes("UTF-8"); - try { - checker.setSafeForMacOS(true); - checker.checkTree(data); - fail("incorrectly accepted an invalid tree"); - } catch (CorruptObjectException e) { - assertEquals("duplicate entry names", e.getMessage()); - } + checker.setSafeForMacOS(true); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); } @Test @@ -1791,7 +1338,7 @@ public void testInvalidTreeDuplicateNames8() @Test public void testRejectNulInPathSegment() { try { - checker.checkPathSegment(Constants.encodeASCII("a\u0000b"), 0, 3); + checker.checkPathSegment(encodeASCII("a\u0000b"), 0, 3); fail("incorrectly accepted NUL in middle of name"); } catch (CorruptObjectException e) { assertEquals("name contains byte 0x00", e.getMessage()); @@ -1893,13 +1440,65 @@ private void rejectName(byte c) { private void checkOneName(String name) throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 " + name); - checker.checkTree(Constants.encodeASCII(b.toString())); + checker.checkTree(encodeASCII(b.toString())); } - private static void entry(final StringBuilder b, final String modeName) { + private static void entry(StringBuilder b, final String modeName) { b.append(modeName); b.append('\0'); - for (int i = 0; i < Constants.OBJECT_ID_LENGTH; i++) + for (int i = 0; i < OBJECT_ID_LENGTH; i++) b.append((char) i); } + + private void assertCorrupt(String msg, int type, StringBuilder b) { + assertCorrupt(msg, type, encodeASCII(b.toString())); + } + + private void assertCorrupt(String msg, int type, byte[] data) { + try { + checker.check(type, data); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException e) { + assertEquals(msg, e.getMessage()); + } + } + + private void assertSkipListAccepts(int type, byte[] data) + throws CorruptObjectException { + ObjectId id = idFor(type, data); + checker.setSkipList(set(id)); + checker.check(id, type, data); + checker.setSkipList(null); + } + + private void assertSkipListRejects(String msg, int type, byte[] data) { + ObjectId id = idFor(type, data); + checker.setSkipList(set(id)); + try { + checker.check(id, type, data); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException e) { + assertEquals(msg, e.getMessage()); + } + checker.setSkipList(null); + } + + private static ObjectIdSet set(final ObjectId... ids) { + return new ObjectIdSet() { + @Override + public boolean contains(AnyObjectId objectId) { + for (ObjectId id : ids) { + if (id.equals(objectId)) { + return true; + } + } + return false; + } + }; + } + + @SuppressWarnings("resource") + private static ObjectId idFor(int type, byte[] raw) { + return new ObjectInserter.Formatter().idFor(type, raw); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java new file mode 100644 index 000000000..1e2617c0e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2015, Google Inc. + * 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.internal.storage.file; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.MutableObjectId; +import org.eclipse.jgit.lib.ObjectIdOwnerMap; +import org.eclipse.jgit.lib.ObjectIdSet; + +/** Lazily loads a set of ObjectIds, one per line. */ +public class LazyObjectIdSetFile implements ObjectIdSet { + private final File src; + private ObjectIdOwnerMap set; + + /** + * Create a new lazy set from a file. + * + * @param src + * the source file. + */ + public LazyObjectIdSetFile(File src) { + this.src = src; + } + + @Override + public boolean contains(AnyObjectId objectId) { + if (set == null) { + set = load(); + } + return set.contains(objectId); + } + + private ObjectIdOwnerMap load() { + ObjectIdOwnerMap r = new ObjectIdOwnerMap<>(); + try (FileInputStream fin = new FileInputStream(src); + Reader rin = new InputStreamReader(fin, UTF_8); + BufferedReader br = new BufferedReader(rin)) { + MutableObjectId id = new MutableObjectId(); + for (String line; (line = br.readLine()) != null;) { + id.fromString(line); + if (!r.contains(id)) { + r.add(new Entry(id)); + } + } + } catch (IOException e) { + // Ignore IO errors accessing the lazy set. + } + return r; + } + + static class Entry extends ObjectIdOwnerMap.Entry { + Entry(AnyObjectId id) { + super(id); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java index 855d9d750..89a526911 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java @@ -44,6 +44,10 @@ package org.eclipse.jgit.lib; +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_TAG; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; import static org.eclipse.jgit.util.RawParseUtils.match; import static org.eclipse.jgit.util.RawParseUtils.nextLF; import static org.eclipse.jgit.util.RawParseUtils.parseBase10; @@ -54,6 +58,7 @@ import java.util.Locale; import java.util.Set; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.MutableInteger; @@ -99,15 +104,28 @@ public class ObjectChecker { public static final byte[] tagger = Constants.encodeASCII("tagger "); //$NON-NLS-1$ private final MutableObjectId tempId = new MutableObjectId(); - private final MutableInteger ptrout = new MutableInteger(); + private ObjectIdSet skipList; private boolean allowZeroMode; - private boolean allowInvalidPersonIdent; private boolean windows; private boolean macosx; + /** + * Enable accepting specific malformed (but not horribly broken) objects. + * + * @param objects + * collection of object names known to be broken in a non-fatal + * way that should be ignored by the checker. + * @return {@code this} + * @since 4.2 + */ + public ObjectChecker setSkipList(@Nullable ObjectIdSet objects) { + skipList = objects; + return this; + } + /** * Enable accepting leading zero mode in tree entries. *

@@ -183,19 +201,40 @@ public ObjectChecker setSafeForMacOS(boolean mac) { * @throws CorruptObjectException * if an error is identified. */ - public void check(final int objType, final byte[] raw) + public void check(int objType, byte[] raw) + throws CorruptObjectException { + check(idFor(objType, raw), objType, raw); + } + + /** + * Check an object for parsing errors. + * + * @param id + * identify of the object being checked. + * @param objType + * type of the object. Must be a valid object type code in + * {@link Constants}. + * @param raw + * the raw data which comprises the object. This should be in the + * canonical format (that is the format used to generate the + * ObjectId of the object). The array is never modified. + * @throws CorruptObjectException + * if an error is identified. + * @since 4.2 + */ + public void check(@Nullable AnyObjectId id, int objType, byte[] raw) throws CorruptObjectException { switch (objType) { - case Constants.OBJ_COMMIT: - checkCommit(raw); + case OBJ_COMMIT: + checkCommit(id, raw); break; - case Constants.OBJ_TAG: - checkTag(raw); + case OBJ_TAG: + checkTag(id, raw); break; - case Constants.OBJ_TREE: - checkTree(raw); + case OBJ_TREE: + checkTree(id, raw); break; - case Constants.OBJ_BLOB: + case OBJ_BLOB: checkBlob(raw); break; default: @@ -214,9 +253,9 @@ private int id(final byte[] raw, final int ptr) { } } - private int personIdent(final byte[] raw, int ptr) { - if (allowInvalidPersonIdent) - return nextLF(raw, ptr) - 1; + private int personIdent(byte[] raw, int ptr, @Nullable AnyObjectId id) { + if (allowInvalidPersonIdent || skip(id)) + return nextLF(raw, ptr); final int emailB = nextLF(raw, ptr, '<'); if (emailB == ptr || raw[emailB - 1] != '<') @@ -238,7 +277,11 @@ private int personIdent(final byte[] raw, int ptr) { parseBase10(raw, ptr + 1, ptrout); // tz offset if (ptr + 1 == ptrout.value) return -1; - return ptrout.value; + + ptr = ptrout.value; + if (raw[ptr++] == '\n') + return ptr; + return -1; } /** @@ -249,7 +292,23 @@ private int personIdent(final byte[] raw, int ptr) { * @throws CorruptObjectException * if any error was detected. */ - public void checkCommit(final byte[] raw) throws CorruptObjectException { + public void checkCommit(byte[] raw) throws CorruptObjectException { + checkCommit(idFor(OBJ_COMMIT, raw), raw); + } + + /** + * Check a commit for errors. + * + * @param id + * identity of the object being checked. + * @param raw + * the commit data. The array is never modified. + * @throws CorruptObjectException + * if any error was detected. + * @since 4.2 + */ + public void checkCommit(@Nullable AnyObjectId id, byte[] raw) + throws CorruptObjectException { int ptr = 0; if ((ptr = match(raw, ptr, tree)) < 0) @@ -266,19 +325,27 @@ public void checkCommit(final byte[] raw) throws CorruptObjectException { JGitText.get().corruptObjectInvalidParent); } - if ((ptr = match(raw, ptr, author)) < 0) + int p = match(raw, ptr, author); + if (p > ptr) { + if ((ptr = personIdent(raw, p, id)) < 0) { + throw new CorruptObjectException( + JGitText.get().corruptObjectInvalidAuthor); + } + } else if (!skip(id)) { throw new CorruptObjectException( JGitText.get().corruptObjectNoAuthor); - if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n') - throw new CorruptObjectException( - JGitText.get().corruptObjectInvalidAuthor); + } - if ((ptr = match(raw, ptr, committer)) < 0) + p = match(raw, ptr, committer); + if (p > ptr) { + if ((ptr = personIdent(raw, p, id)) < 0) { + throw new CorruptObjectException( + JGitText.get().corruptObjectInvalidCommitter); + } + } else if (!skip(id)) { throw new CorruptObjectException( JGitText.get().corruptObjectNoCommitter); - if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n') - throw new CorruptObjectException( - JGitText.get().corruptObjectInvalidCommitter); + } } /** @@ -289,7 +356,23 @@ public void checkCommit(final byte[] raw) throws CorruptObjectException { * @throws CorruptObjectException * if any error was detected. */ - public void checkTag(final byte[] raw) throws CorruptObjectException { + public void checkTag(byte[] raw) throws CorruptObjectException { + checkTag(idFor(OBJ_TAG, raw), raw); + } + + /** + * Check an annotated tag for errors. + * + * @param id + * identity of the object being checked. + * @param raw + * the tag data. The array is never modified. + * @throws CorruptObjectException + * if any error was detected. + * @since 4.2 + */ + public void checkTag(@Nullable AnyObjectId id, byte[] raw) + throws CorruptObjectException { int ptr = 0; if ((ptr = match(raw, ptr, object)) < 0) @@ -304,15 +387,16 @@ public void checkTag(final byte[] raw) throws CorruptObjectException { JGitText.get().corruptObjectNoTypeHeader); ptr = nextLF(raw, ptr); - if ((ptr = match(raw, ptr, tag)) < 0) + if (match(raw, ptr, tag) < 0 && !skip(id)) throw new CorruptObjectException( JGitText.get().corruptObjectNoTagHeader); ptr = nextLF(raw, ptr); if ((ptr = match(raw, ptr, tagger)) > 0) { - if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n') + if ((ptr = personIdent(raw, ptr, id)) < 0) { throw new CorruptObjectException( JGitText.get().corruptObjectInvalidTagger); + } } } @@ -381,11 +465,28 @@ else if (cmp == 0) * @throws CorruptObjectException * if any error was detected. */ - public void checkTree(final byte[] raw) throws CorruptObjectException { + public void checkTree(byte[] raw) throws CorruptObjectException { + checkTree(idFor(OBJ_TREE, raw), raw); + } + + /** + * Check a canonical formatted tree for errors. + * + * @param id + * identity of the object being checked. + * @param raw + * the raw tree data. The array is never modified. + * @throws CorruptObjectException + * if any error was detected. + * @since 4.2 + */ + public void checkTree(@Nullable AnyObjectId id, byte[] raw) + throws CorruptObjectException { final int sz = raw.length; int ptr = 0; int lastNameB = 0, lastNameE = 0, lastMode = 0; - Set normalized = windows || macosx + boolean skip = skip(id); + Set normalized = !skip && (windows || macosx) ? new HashSet() : null; @@ -401,7 +502,7 @@ public void checkTree(final byte[] raw) throws CorruptObjectException { if (c < '0' || c > '7') throw new CorruptObjectException( JGitText.get().corruptObjectInvalidModeChar); - if (thisMode == 0 && c == '0' && !allowZeroMode) + if (thisMode == 0 && c == '0' && !allowZeroMode && !skip) throw new CorruptObjectException( JGitText.get().corruptObjectInvalidModeStartsZero); thisMode <<= 3; @@ -418,16 +519,16 @@ public void checkTree(final byte[] raw) throws CorruptObjectException { if (ptr == sz || raw[ptr] != 0) throw new CorruptObjectException( JGitText.get().corruptObjectTruncatedInName); - checkPathSegment2(raw, thisNameB, ptr); + checkPathSegment2(raw, thisNameB, ptr, skip); if (normalized != null) { if (!normalized.add(normalize(raw, thisNameB, ptr))) throw new CorruptObjectException( JGitText.get().corruptObjectDuplicateEntryNames); - } else if (duplicateName(raw, thisNameB, ptr)) + } else if (!skip && duplicateName(raw, thisNameB, ptr)) throw new CorruptObjectException( JGitText.get().corruptObjectDuplicateEntryNames); - if (lastNameB != 0) { + if (!skip && lastNameB != 0) { final int cmp = pathCompare(raw, lastNameB, lastNameE, lastMode, thisNameB, ptr, thisMode); if (cmp > 0) @@ -468,6 +569,19 @@ private int scanPathSegment(byte[] raw, int ptr, int end) return ptr; } + @SuppressWarnings("resource") + @Nullable + private ObjectId idFor(int objType, byte[] raw) { + if (skipList != null) { + return new ObjectInserter.Formatter().idFor(objType, raw); + } + return null; + } + + private boolean skip(@Nullable AnyObjectId id) { + return skipList != null && id != null && skipList.contains(id); + } + /** * Check tree path entry for validity. *

@@ -522,10 +636,10 @@ public void checkPathSegment(byte[] raw, int ptr, int end) if (e < end && raw[e] == 0) throw new CorruptObjectException( JGitText.get().corruptObjectNameContainsNullByte); - checkPathSegment2(raw, ptr, end); + checkPathSegment2(raw, ptr, end, false); } - private void checkPathSegment2(byte[] raw, int ptr, int end) + private void checkPathSegment2(byte[] raw, int ptr, int end, boolean skip) throws CorruptObjectException { if (ptr == end) throw new CorruptObjectException( @@ -541,36 +655,38 @@ private void checkPathSegment2(byte[] raw, int ptr, int end) JGitText.get().corruptObjectNameDotDot); break; case 4: - if (isGit(raw, ptr + 1)) + if (!skip && isGit(raw, ptr + 1)) throw new CorruptObjectException(String.format( JGitText.get().corruptObjectInvalidName, RawParseUtils.decode(raw, ptr, end))); break; default: - if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) + if (!skip && end - ptr > 4 + && isNormalizedGit(raw, ptr + 1, end)) throw new CorruptObjectException(String.format( JGitText.get().corruptObjectInvalidName, RawParseUtils.decode(raw, ptr, end))); } - } else if (isGitTilde1(raw, ptr, end)) { + } else if (!skip && isGitTilde1(raw, ptr, end)) { throw new CorruptObjectException(String.format( JGitText.get().corruptObjectInvalidName, RawParseUtils.decode(raw, ptr, end))); } - - if (macosx && isMacHFSGit(raw, ptr, end)) - throw new CorruptObjectException(String.format( - JGitText.get().corruptObjectInvalidNameIgnorableUnicode, - RawParseUtils.decode(raw, ptr, end))); - - if (windows) { - // Windows ignores space and dot at end of file name. - if (raw[end - 1] == ' ' || raw[end - 1] == '.') + if (!skip) { + if (macosx && isMacHFSGit(raw, ptr, end)) throw new CorruptObjectException(String.format( - JGitText.get().corruptObjectInvalidNameEnd, - Character.valueOf(((char) raw[end - 1])))); - if (end - ptr >= 3) - checkNotWindowsDevice(raw, ptr, end); + JGitText.get().corruptObjectInvalidNameIgnorableUnicode, + RawParseUtils.decode(raw, ptr, end))); + + if (windows) { + // Windows ignores space and dot at end of file name. + if (raw[end - 1] == ' ' || raw[end - 1] == '.') + throw new CorruptObjectException(String.format( + JGitText.get().corruptObjectInvalidNameEnd, + Character.valueOf(((char) raw[end - 1])))); + if (end - ptr >= 3) + checkNotWindowsDevice(raw, ptr, end); + } } } 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 6e5fc9f00..42816bd68 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java @@ -1049,7 +1049,7 @@ private void verifySafeObject(final AnyObjectId id, final int type, final byte[] data) throws IOException { if (objCheck != null) { try { - objCheck.check(type, data); + objCheck.check(id, type, data); } catch (CorruptObjectException e) { throw new CorruptObjectException(MessageFormat.format( JGitText.get().invalidObject, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java index f9b74c84e..2128f1f7e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java @@ -43,13 +43,16 @@ package org.eclipse.jgit.transport; +import java.io.File; import java.util.HashMap; import java.util.Map; import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.storage.file.LazyObjectIdSetFile; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.ObjectChecker; +import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.SystemReader; @@ -68,6 +71,7 @@ public TransferConfig parse(final Config cfg) { private final boolean fetchFsck; private final boolean receiveFsck; + private final String fsckSkipList; private final boolean allowLeadingZeroFileMode; private final boolean allowInvalidPersonIdent; private final boolean safeForWindows; @@ -84,6 +88,7 @@ public TransferConfig parse(final Config cfg) { boolean fsck = rc.getBoolean("transfer", "fsckobjects", false); //$NON-NLS-1$ //$NON-NLS-2$ fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ + fsckSkipList = rc.getString("fsck", null, "skipList"); //$NON-NLS-1$ //$NON-NLS-2$ allowLeadingZeroFileMode = rc.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$ allowInvalidPersonIdent = rc.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$ safeForWindows = rc.getBoolean("fsck", "safeForWindows", //$NON-NLS-1$ //$NON-NLS-2$ @@ -126,7 +131,15 @@ private ObjectChecker newObjectChecker(boolean check) { .setAllowLeadingZeroFileMode(allowLeadingZeroFileMode) .setAllowInvalidPersonIdent(allowInvalidPersonIdent) .setSafeForWindows(safeForWindows) - .setSafeForMacOS(safeForMacOS); + .setSafeForMacOS(safeForMacOS) + .setSkipList(skipList()); + } + + private ObjectIdSet skipList() { + if (fsckSkipList != null && !fsckSkipList.isEmpty()) { + return new LazyObjectIdSetFile(new File(fsckSkipList)); + } + return null; } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java index dfc3ee4c3..17edfdc4f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -637,10 +637,11 @@ private void verifyAndInsertLooseObject(final AnyObjectId id, final byte[] raw = uol.getCachedBytes(); if (objCheck != null) { try { - objCheck.check(type, raw); + objCheck.check(id, type, raw); } catch (CorruptObjectException e) { - throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionInvalid - , Constants.typeString(type), id.name(), e.getMessage())); + throw new TransportException(MessageFormat.format( + JGitText.get().transportExceptionInvalid, + Constants.typeString(type), id.name(), e.getMessage())); } } From ac41920a430941b9385889d19e0fc2764ba23474 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Tue, 29 Dec 2015 18:21:19 -0800 Subject: [PATCH 44/89] ObjectChecker: honor some git-core fsck.* options Accept some of the same section keys that fsck does in git-core, allowing repositories to skip over specific kinds of acceptable broken objects, e.g.: [fsck] duplicateEntries = ignore zeroPaddedFilemode = ignore The zeroPaddedFilemode = ignore is a synonym for the JGit specific allowLeadingZeroFileMode = true. Only accept the JGit key if git-core key was not specified. Change-Id: Idaed9310e2a5ce5511670ead1aaea2b30aac903c --- .../eclipse/jgit/lib/ObjectCheckerTest.java | 148 ++++- .../eclipse/jgit/internal/JGitText.properties | 9 +- .../jgit/errors/CorruptObjectException.java | 45 +- .../org/eclipse/jgit/internal/JGitText.java | 9 +- .../org/eclipse/jgit/lib/ObjectChecker.java | 507 ++++++++++++------ .../eclipse/jgit/transport/PackParser.java | 3 + .../jgit/transport/TransferConfig.java | 84 ++- 7 files changed, 600 insertions(+), 205 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java index 951930309..80230dccf 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java @@ -53,7 +53,17 @@ import static org.eclipse.jgit.lib.Constants.OBJ_TREE; import static org.eclipse.jgit.lib.Constants.encode; import static org.eclipse.jgit.lib.Constants.encodeASCII; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; import java.io.UnsupportedEncodingException; @@ -130,7 +140,7 @@ public void testCommitCorruptAuthor() throws CorruptObjectException { b.append("committer <> 0 +0000\n"); byte[] data = encodeASCII(b.toString()); - assertCorrupt("invalid author", OBJ_COMMIT, data); + assertCorrupt("bad date", OBJ_COMMIT, data); checker.setAllowInvalidPersonIdent(true); checker.checkCommit(data); @@ -146,7 +156,7 @@ public void testCommitCorruptCommitter() throws CorruptObjectException { b.append("committer b 0 +0000\n"); byte[] data = encodeASCII(b.toString()); - assertCorrupt("invalid committer", OBJ_COMMIT, data); + assertCorrupt("bad date", OBJ_COMMIT, data); checker.setAllowInvalidPersonIdent(true); checker.checkCommit(data); @@ -300,7 +310,6 @@ public void testInvalidCommitInvalidTree3() { byte[] data = encodeASCII(b.toString()); assertCorrupt("invalid tree", OBJ_COMMIT, data); - assertSkipListRejects("invalid tree", OBJ_COMMIT, data); } @Test @@ -425,7 +434,7 @@ public void testInvalidCommitInvalidAuthor1() b.append("author A. U. Thor 1 +0000\n"); byte[] data = encodeASCII(b.toString()); - assertCorrupt("invalid author", OBJ_COMMIT, data); + assertCorrupt("missing email", OBJ_COMMIT, data); assertSkipListAccepts(OBJ_COMMIT, data); } @@ -453,7 +462,7 @@ public void testInvalidCommitInvalidAuthor3() b.append("author 1 +0000\n"); byte[] data = encodeASCII(b.toString()); - assertCorrupt("invalid author", OBJ_COMMIT, data); + assertCorrupt("missing email", OBJ_COMMIT, data); assertSkipListAccepts(OBJ_COMMIT, data); } @@ -467,7 +476,7 @@ public void testInvalidCommitInvalidAuthor4() b.append("author a +0000\n"); byte[] data = encodeASCII(b.toString()); - assertCorrupt("invalid author", OBJ_COMMIT, data); + assertCorrupt("bad date", OBJ_COMMIT, data); assertSkipListAccepts(OBJ_COMMIT, data); } @@ -481,7 +490,7 @@ public void testInvalidCommitInvalidAuthor5() b.append("author a \n"); byte[] data = encodeASCII(b.toString()); - assertCorrupt("invalid author", OBJ_COMMIT, data); + assertCorrupt("bad date", OBJ_COMMIT, data); assertSkipListAccepts(OBJ_COMMIT, data); } @@ -495,7 +504,7 @@ public void testInvalidCommitInvalidAuthor6() b.append("author a z"); byte[] data = encodeASCII(b.toString()); - assertCorrupt("invalid author", OBJ_COMMIT, data); + assertCorrupt("bad date", OBJ_COMMIT, data); assertSkipListAccepts(OBJ_COMMIT, data); } @@ -509,7 +518,7 @@ public void testInvalidCommitInvalidAuthor7() b.append("author a 1 z"); byte[] data = encodeASCII(b.toString()); - assertCorrupt("invalid author", OBJ_COMMIT, data); + assertCorrupt("bad time zone", OBJ_COMMIT, data); assertSkipListAccepts(OBJ_COMMIT, data); } @@ -524,7 +533,7 @@ public void testInvalidCommitInvalidCommitter() b.append("committer a <"); byte[] data = encodeASCII(b.toString()); - assertCorrupt("invalid committer", OBJ_COMMIT, data); + assertCorrupt("bad email", OBJ_COMMIT, data); assertSkipListAccepts(OBJ_COMMIT, data); } @@ -686,7 +695,7 @@ public void testInvalidTagInvalidTaggerHeader1() b.append("tagger \n"); byte[] data = encodeASCII(b.toString()); - assertCorrupt("invalid tagger", OBJ_TAG, data); + assertCorrupt("missing email", OBJ_TAG, data); checker.setAllowInvalidPersonIdent(true); checker.checkTag(data); @@ -706,7 +715,7 @@ public void testInvalidTagInvalidTaggerHeader3() b.append("tagger a < 1 +000\n"); byte[] data = encodeASCII(b.toString()); - assertCorrupt("invalid tagger", OBJ_TAG, data); + assertCorrupt("bad email", OBJ_TAG, data); assertSkipListAccepts(OBJ_TAG, data); } @@ -758,6 +767,17 @@ public void testValidTree6() throws CorruptObjectException { checker.checkTree(encodeASCII(b.toString())); } + @Test + public void testNullSha1InTreeEntry() throws CorruptObjectException { + byte[] data = concat( + encodeASCII("100644 A"), new byte[] { '\0' }, + new byte[OBJECT_ID_LENGTH]); + assertCorrupt("entry points to null SHA-1", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(NULL_SHA1, true); + checker.checkTree(data); + } + @Test public void testValidPosixTree() throws CorruptObjectException { checkOneName("ac:d|e"); @@ -842,6 +862,9 @@ public void testAcceptTreeModeWithZero() throws CorruptObjectException { checker.setAllowLeadingZeroFileMode(false); assertSkipListAccepts(OBJ_TREE, data); + + checker.setIgnore(ZERO_PADDED_FILEMODE, true); + checker.checkTree(data); } @Test @@ -905,39 +928,48 @@ public void testInvalidTreeModeMissingName() { } @Test - public void testInvalidTreeNameContainsSlash() { + public void testInvalidTreeNameContainsSlash() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 a/b"); byte[] data = encodeASCII(b.toString()); assertCorrupt("name contains '/'", OBJ_TREE, data); - assertSkipListRejects("name contains '/'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(FULL_PATHNAME, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsEmpty() { + public void testInvalidTreeNameIsEmpty() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 "); byte[] data = encodeASCII(b.toString()); assertCorrupt("zero length name", OBJ_TREE, data); - assertSkipListRejects("zero length name", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(EMPTY_NAME, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsDot() { + public void testInvalidTreeNameIsDot() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 ."); byte[] data = encodeASCII(b.toString()); assertCorrupt("invalid name '.'", OBJ_TREE, data); - assertSkipListRejects("invalid name '.'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOT, true); + checker.checkTree(data); } @Test - public void testInvalidTreeNameIsDotDot() { + public void testInvalidTreeNameIsDotDot() throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 .."); byte[] data = encodeASCII(b.toString()); assertCorrupt("invalid name '..'", OBJ_TREE, data); - assertSkipListRejects("invalid name '..'", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTDOT, true); + checker.checkTree(data); } @Test @@ -947,6 +979,8 @@ public void testInvalidTreeNameIsGit() throws CorruptObjectException { byte[] data = encodeASCII(b.toString()); assertCorrupt("invalid name '.git'", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -957,6 +991,8 @@ public void testInvalidTreeNameIsMixedCaseGit() byte[] data = encodeASCII(b.toString()); assertCorrupt("invalid name '.GiT'", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -974,6 +1010,8 @@ public void testInvalidTreeNameIsMacHFSGit() throws CorruptObjectException { "invalid name '.gi\u200Ct' contains ignorable Unicode characters", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -992,6 +1030,8 @@ public void testInvalidTreeNameIsMacHFSGit2() "invalid name '\u206B.git' contains ignorable Unicode characters", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -1010,12 +1050,22 @@ public void testInvalidTreeNameIsMacHFSGit3() "invalid name '.git\uFEFF' contains ignorable Unicode characters", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } - private static byte[] concat(byte[] b1, byte[] b2) { - byte[] data = new byte[b1.length + b2.length]; - System.arraycopy(b1, 0, data, 0, b1.length); - System.arraycopy(b2, 0, data, b1.length, b2.length); + private static byte[] concat(byte[]... b) { + int n = 0; + for (byte[] a : b) { + n += a.length; + } + + byte[] data = new byte[n]; + n = 0; + for (byte[] a : b) { + System.arraycopy(a, 0, data, n, a.length); + n += a.length; + } return data; } @@ -1096,6 +1146,8 @@ public void testInvalidTreeNameIsDotGitDot() throws CorruptObjectException { byte[] data = encodeASCII(b.toString()); assertCorrupt("invalid name '.git.'", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -1114,6 +1166,8 @@ public void testInvalidTreeNameIsDotGitSpace() byte[] data = encodeASCII(b.toString()); assertCorrupt("invalid name '.git '", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -1160,6 +1214,8 @@ public void testInvalidTreeNameIsDotGitDotSpace() byte[] data = encodeASCII(b.toString()); assertCorrupt("invalid name '.git. '", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -1170,6 +1226,8 @@ public void testInvalidTreeNameIsDotGitSpaceDot() byte[] data = encodeASCII(b.toString()); assertCorrupt("invalid name '.git . '", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -1179,6 +1237,8 @@ public void testInvalidTreeNameIsGITTilde1() throws CorruptObjectException { byte[] data = encodeASCII(b.toString()); assertCorrupt("invalid name 'GIT~1'", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -1188,6 +1248,8 @@ public void testInvalidTreeNameIsGiTTilde1() throws CorruptObjectException { byte[] data = encodeASCII(b.toString()); assertCorrupt("invalid name 'GiT~1'", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(HAS_DOTGIT, true); + checker.checkTree(data); } @Test @@ -1222,8 +1284,22 @@ public void testInvalidTreeBadSorting1() throws CorruptObjectException { entry(b, "100644 foobar"); entry(b, "100644 fooaaa"); byte[] data = encodeASCII(b.toString()); + assertCorrupt("incorrectly sorted", OBJ_TREE, data); + + ObjectId id = idFor(OBJ_TREE, data); + try { + checker.check(id, OBJ_TREE, data); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException e) { + assertSame(TREE_NOT_SORTED, e.getErrorType()); + assertEquals("treeNotSorted: object " + id.name() + + ": incorrectly sorted", e.getMessage()); + } + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(TREE_NOT_SORTED, true); + checker.checkTree(data); } @Test @@ -1234,6 +1310,8 @@ public void testInvalidTreeBadSorting2() throws CorruptObjectException { byte[] data = encodeASCII(b.toString()); assertCorrupt("incorrectly sorted", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(TREE_NOT_SORTED, true); + checker.checkTree(data); } @Test @@ -1244,6 +1322,8 @@ public void testInvalidTreeBadSorting3() throws CorruptObjectException { byte[] data = encodeASCII(b.toString()); assertCorrupt("incorrectly sorted", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(TREE_NOT_SORTED, true); + checker.checkTree(data); } @Test @@ -1254,6 +1334,8 @@ public void testInvalidTreeDuplicateNames1() throws CorruptObjectException { byte[] data = encodeASCII(b.toString()); assertCorrupt("duplicate entry names", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test @@ -1264,6 +1346,8 @@ public void testInvalidTreeDuplicateNames2() throws CorruptObjectException { byte[] data = encodeASCII(b.toString()); assertCorrupt("duplicate entry names", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test @@ -1274,6 +1358,8 @@ public void testInvalidTreeDuplicateNames3() throws CorruptObjectException { byte[] data = encodeASCII(b.toString()); assertCorrupt("duplicate entry names", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test @@ -1288,30 +1374,36 @@ public void testInvalidTreeDuplicateNames4() throws CorruptObjectException { byte[] data = encodeASCII(b.toString()); assertCorrupt("duplicate entry names", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test public void testInvalidTreeDuplicateNames5() throws UnsupportedEncodingException, CorruptObjectException { StringBuilder b = new StringBuilder(); - entry(b, "100644 a"); entry(b, "100644 A"); + entry(b, "100644 a"); byte[] data = b.toString().getBytes("UTF-8"); checker.setSafeForWindows(true); assertCorrupt("duplicate entry names", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test public void testInvalidTreeDuplicateNames6() throws UnsupportedEncodingException, CorruptObjectException { StringBuilder b = new StringBuilder(); - entry(b, "100644 a"); entry(b, "100644 A"); + entry(b, "100644 a"); byte[] data = b.toString().getBytes("UTF-8"); checker.setSafeForMacOS(true); assertCorrupt("duplicate entry names", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test @@ -1324,6 +1416,8 @@ public void testInvalidTreeDuplicateNames7() checker.setSafeForMacOS(true); assertCorrupt("duplicate entry names", OBJ_TREE, data); assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); } @Test diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 840059d34..992e10bad 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -126,14 +126,15 @@ connectionFailed=connection failed connectionTimeOut=Connection time out: {0} contextMustBeNonNegative=context must be >= 0 corruptionDetectedReReadingAt=Corruption detected re-reading at {0} +corruptObjectBadDate=bad date +corruptObjectBadEmail=bad email corruptObjectBadStream=bad stream corruptObjectBadStreamCorruptHeader=bad stream, corrupt header +corruptObjectBadTimezone=bad time zone corruptObjectDuplicateEntryNames=duplicate entry names corruptObjectGarbageAfterSize=garbage after size corruptObjectIncorrectLength=incorrect length corruptObjectIncorrectSorting=incorrectly sorted -corruptObjectInvalidAuthor=invalid author -corruptObjectInvalidCommitter=invalid committer corruptObjectInvalidEntryMode=invalid entry mode corruptObjectInvalidMode=invalid mode corruptObjectInvalidModeChar=invalid mode character @@ -152,11 +153,11 @@ corruptObjectInvalidNameNul=invalid name 'NUL' corruptObjectInvalidNamePrn=invalid name 'PRN' corruptObjectInvalidObject=invalid object corruptObjectInvalidParent=invalid parent -corruptObjectInvalidTagger=invalid tagger corruptObjectInvalidTree=invalid tree corruptObjectInvalidType=invalid type corruptObjectInvalidType2=invalid type {0} corruptObjectMalformedHeader=malformed header: {0} +corruptObjectMissingEmail=missing email corruptObjectNameContainsByte=name contains byte 0x%x corruptObjectNameContainsChar=name contains '%c' corruptObjectNameContainsNullByte=name contains byte 0x00 @@ -182,6 +183,7 @@ corruptObjectPackfileChecksumIncorrect=Packfile checksum incorrect. corruptObjectTruncatedInMode=truncated in mode corruptObjectTruncatedInName=truncated in name corruptObjectTruncatedInObjectId=truncated in object id +corruptObjectZeroId=entry points to null SHA-1 couldNotCheckOutBecauseOfConflicts=Could not check out because of conflicts couldNotDeleteLockFileShouldNotHappen=Could not delete lock file. Should not happen couldNotDeleteTemporaryIndexFileShouldNotHappen=Could not delete temporary index file. Should not happen @@ -433,6 +435,7 @@ noXMLParserAvailable=No XML parser available. objectAtHasBadZlibStream=Object at {0} in {1} has bad zlib stream objectAtPathDoesNotHaveId=Object at path "{0}" does not have an id assigned. All object ids must be assigned prior to writing a tree. objectIsCorrupt=Object {0} is corrupt: {1} +objectIsCorrupt3={0}: object {1}: {2} objectIsNotA=Object {0} is not a {1}. objectNotFound=Object {0} not found. objectNotFoundIn=Object {0} not found in {1}. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java index c6ea09375..e4db40b88 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java @@ -49,8 +49,10 @@ import java.io.IOException; import java.text.MessageFormat; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectId; /** @@ -59,15 +61,24 @@ public class CorruptObjectException extends IOException { private static final long serialVersionUID = 1L; + private ObjectChecker.ErrorType errorType; + /** - * Construct a CorruptObjectException for reporting a problem specified - * object id + * Report a specific error condition discovered in an object. * + * @param type + * type of error * @param id + * identity of the bad object * @param why + * description of the error. + * @since 4.2 */ - public CorruptObjectException(final AnyObjectId id, final String why) { - this(id.toObjectId(), why); + public CorruptObjectException(ObjectChecker.ErrorType type, AnyObjectId id, + String why) { + super(MessageFormat.format(JGitText.get().objectIsCorrupt3, + type.getMessageId(), id.name(), why)); + this.errorType = type; } /** @@ -77,7 +88,18 @@ public CorruptObjectException(final AnyObjectId id, final String why) { * @param id * @param why */ - public CorruptObjectException(final ObjectId id, final String why) { + public CorruptObjectException(AnyObjectId id, String why) { + super(MessageFormat.format(JGitText.get().objectIsCorrupt, id.name(), why)); + } + + /** + * Construct a CorruptObjectException for reporting a problem specified + * object id + * + * @param id + * @param why + */ + public CorruptObjectException(ObjectId id, String why) { super(MessageFormat.format(JGitText.get().objectIsCorrupt, id.name(), why)); } @@ -87,7 +109,7 @@ public CorruptObjectException(final ObjectId id, final String why) { * * @param why */ - public CorruptObjectException(final String why) { + public CorruptObjectException(String why) { super(why); } @@ -105,4 +127,15 @@ public CorruptObjectException(String why, Throwable cause) { super(why); initCause(cause); } + + /** + * Specific error condition identified by {@link ObjectChecker}. + * + * @return error condition or null. + * @since 4.2 + */ + @Nullable + public ObjectChecker.ErrorType getErrorType() { + return errorType; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index c476f1773..7740a2bb8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -185,14 +185,15 @@ public static JGitText get() { /***/ public String connectionTimeOut; /***/ public String contextMustBeNonNegative; /***/ public String corruptionDetectedReReadingAt; + /***/ public String corruptObjectBadDate; + /***/ public String corruptObjectBadEmail; /***/ public String corruptObjectBadStream; /***/ public String corruptObjectBadStreamCorruptHeader; + /***/ public String corruptObjectBadTimezone; /***/ public String corruptObjectDuplicateEntryNames; /***/ public String corruptObjectGarbageAfterSize; /***/ public String corruptObjectIncorrectLength; /***/ public String corruptObjectIncorrectSorting; - /***/ public String corruptObjectInvalidAuthor; - /***/ public String corruptObjectInvalidCommitter; /***/ public String corruptObjectInvalidEntryMode; /***/ public String corruptObjectInvalidMode; /***/ public String corruptObjectInvalidModeChar; @@ -211,11 +212,11 @@ public static JGitText get() { /***/ public String corruptObjectInvalidNamePrn; /***/ public String corruptObjectInvalidObject; /***/ public String corruptObjectInvalidParent; - /***/ public String corruptObjectInvalidTagger; /***/ public String corruptObjectInvalidTree; /***/ public String corruptObjectInvalidType; /***/ public String corruptObjectInvalidType2; /***/ public String corruptObjectMalformedHeader; + /***/ public String corruptObjectMissingEmail; /***/ public String corruptObjectNameContainsByte; /***/ public String corruptObjectNameContainsChar; /***/ public String corruptObjectNameContainsNullByte; @@ -241,6 +242,7 @@ public static JGitText get() { /***/ public String corruptObjectTruncatedInMode; /***/ public String corruptObjectTruncatedInName; /***/ public String corruptObjectTruncatedInObjectId; + /***/ public String corruptObjectZeroId; /***/ public String corruptPack; /***/ public String couldNotCheckOutBecauseOfConflicts; /***/ public String couldNotDeleteLockFileShouldNotHappen; @@ -492,6 +494,7 @@ public static JGitText get() { /***/ public String objectAtHasBadZlibStream; /***/ public String objectAtPathDoesNotHaveId; /***/ public String objectIsCorrupt; + /***/ public String objectIsCorrupt3; /***/ public String objectIsNotA; /***/ public String objectNotFound; /***/ public String objectNotFoundIn; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java index 89a526911..858a0385d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java @@ -44,25 +44,56 @@ package org.eclipse.jgit.lib; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH; +import static org.eclipse.jgit.lib.Constants.OBJ_BAD; 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_TAG; import static org.eclipse.jgit.lib.Constants.OBJ_TREE; -import static org.eclipse.jgit.util.RawParseUtils.match; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_DATE; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_EMAIL; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_OBJECT_SHA1; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_PARENT_SHA1; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TIMEZONE; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TREE_SHA1; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_UTF8; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_AUTHOR; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_COMMITTER; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_EMAIL; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_OBJECT; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_SPACE_BEFORE_DATE; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TAG_ENTRY; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TREE; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TYPE_ENTRY; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.UNKNOWN_TYPE; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.WIN32_BAD_NAME; +import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE; import static org.eclipse.jgit.util.RawParseUtils.nextLF; import static org.eclipse.jgit.util.RawParseUtils.parseBase10; import java.text.MessageFormat; import java.text.Normalizer; +import java.util.EnumSet; import java.util.HashSet; import java.util.Locale; import java.util.Set; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.StringUtils; /** * Verifies that an object is formatted correctly. @@ -103,11 +134,65 @@ public class ObjectChecker { /** Header "tagger " */ public static final byte[] tagger = Constants.encodeASCII("tagger "); //$NON-NLS-1$ - private final MutableObjectId tempId = new MutableObjectId(); - private final MutableInteger ptrout = new MutableInteger(); + /** + * Potential issues identified by the checker. + * + * @since 4.2 + */ + public enum ErrorType { + // @formatter:off + // These names match git-core so that fsck section keys also match. + /***/ NULL_SHA1, + /***/ DUPLICATE_ENTRIES, + /***/ TREE_NOT_SORTED, + /***/ ZERO_PADDED_FILEMODE, + /***/ EMPTY_NAME, + /***/ FULL_PATHNAME, + /***/ HAS_DOT, + /***/ HAS_DOTDOT, + /***/ HAS_DOTGIT, + /***/ BAD_OBJECT_SHA1, + /***/ BAD_PARENT_SHA1, + /***/ BAD_TREE_SHA1, + /***/ MISSING_AUTHOR, + /***/ MISSING_COMMITTER, + /***/ MISSING_OBJECT, + /***/ MISSING_TREE, + /***/ MISSING_TYPE_ENTRY, + /***/ MISSING_TAG_ENTRY, + /***/ BAD_DATE, + /***/ BAD_EMAIL, + /***/ BAD_TIMEZONE, + /***/ MISSING_EMAIL, + /***/ MISSING_SPACE_BEFORE_DATE, + /***/ UNKNOWN_TYPE, + // These are unique to JGit. + /***/ WIN32_BAD_NAME, + /***/ BAD_UTF8; + // @formatter:on + + /** @return camelCaseVersion of the name. */ + public String getMessageId() { + String n = name(); + StringBuilder r = new StringBuilder(n.length()); + for (int i = 0; i < n.length(); i++) { + char c = n.charAt(i); + if (c != '_') { + r.append(StringUtils.toLowerCase(c)); + } else { + r.append(n.charAt(++i)); + } + } + return r.toString(); + } + } + + private final MutableObjectId tempId = new MutableObjectId(); + private final MutableInteger bufPtr = new MutableInteger(); + + private EnumSet errors = EnumSet.allOf(ErrorType.class); private ObjectIdSet skipList; - private boolean allowZeroMode; private boolean allowInvalidPersonIdent; private boolean windows; private boolean macosx; @@ -126,6 +211,42 @@ public ObjectChecker setSkipList(@Nullable ObjectIdSet objects) { return this; } + /** + * Configure error types to be ignored across all objects. + * + * @param ids + * error types to ignore. The caller's set is copied. + * @return {@code this} + * @since 4.2 + */ + public ObjectChecker setIgnore(@Nullable Set ids) { + errors = EnumSet.allOf(ErrorType.class); + if (ids != null) { + errors.removeAll(ids); + } + return this; + } + + /** + * Add message type to be ignored across all objects. + * + * @param id + * error type to ignore. + * @param ignore + * true to ignore this error; false to treat the error as an + * error and throw. + * @return {@code this} + * @since 4.2 + */ + public ObjectChecker setIgnore(ErrorType id, boolean ignore) { + if (ignore) { + errors.remove(id); + } else { + errors.add(id); + } + return this; + } + /** * Enable accepting leading zero mode in tree entries. *

@@ -133,14 +254,15 @@ public ObjectChecker setSkipList(@Nullable ObjectIdSet objects) { * tree entries. This is technically incorrect but gracefully allowed by * git-core. JGit rejects such trees by default, but may need to accept * them on broken histories. + *

+ * Same as {@code setIgnore(ZERO_PADDED_FILEMODE, allow)}. * * @param allow allow leading zero mode. * @return {@code this}. * @since 3.4 */ public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) { - allowZeroMode = allow; - return this; + return setIgnore(ZERO_PADDED_FILEMODE, allow); } /** @@ -238,50 +360,80 @@ public void check(@Nullable AnyObjectId id, int objType, byte[] raw) checkBlob(raw); break; default: - throw new CorruptObjectException(MessageFormat.format( + report(UNKNOWN_TYPE, id, MessageFormat.format( JGitText.get().corruptObjectInvalidType2, Integer.valueOf(objType))); } } - private int id(final byte[] raw, final int ptr) { + private boolean checkId(byte[] raw) { + int p = bufPtr.value; try { - tempId.fromString(raw, ptr); - return ptr + Constants.OBJECT_ID_STRING_LENGTH; + tempId.fromString(raw, p); } catch (IllegalArgumentException e) { - return -1; + bufPtr.value = nextLF(raw, p); + return false; } + + p += OBJECT_ID_STRING_LENGTH; + if (raw[p] == '\n') { + bufPtr.value = p + 1; + return true; + } + bufPtr.value = nextLF(raw, p); + return false; } - private int personIdent(byte[] raw, int ptr, @Nullable AnyObjectId id) { - if (allowInvalidPersonIdent || skip(id)) - return nextLF(raw, ptr); + private void checkPersonIdent(byte[] raw, @Nullable AnyObjectId id) + throws CorruptObjectException { + if (allowInvalidPersonIdent) { + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } - final int emailB = nextLF(raw, ptr, '<'); - if (emailB == ptr || raw[emailB - 1] != '<') - return -1; + final int emailB = nextLF(raw, bufPtr.value, '<'); + if (emailB == bufPtr.value || raw[emailB - 1] != '<') { + report(MISSING_EMAIL, id, JGitText.get().corruptObjectMissingEmail); + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } final int emailE = nextLF(raw, emailB, '>'); - if (emailE == emailB || raw[emailE - 1] != '>') - return -1; - if (emailE == raw.length || raw[emailE] != ' ') - return -1; + if (emailE == emailB || raw[emailE - 1] != '>') { + report(BAD_EMAIL, id, JGitText.get().corruptObjectBadEmail); + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } + if (emailE == raw.length || raw[emailE] != ' ') { + report(MISSING_SPACE_BEFORE_DATE, id, + JGitText.get().corruptObjectBadDate); + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } - parseBase10(raw, emailE + 1, ptrout); // when - ptr = ptrout.value; - if (emailE + 1 == ptr) - return -1; - if (ptr == raw.length || raw[ptr] != ' ') - return -1; + parseBase10(raw, emailE + 1, bufPtr); // when + if (emailE + 1 == bufPtr.value || bufPtr.value == raw.length + || raw[bufPtr.value] != ' ') { + report(BAD_DATE, id, JGitText.get().corruptObjectBadDate); + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } - parseBase10(raw, ptr + 1, ptrout); // tz offset - if (ptr + 1 == ptrout.value) - return -1; + int p = bufPtr.value + 1; + parseBase10(raw, p, bufPtr); // tz offset + if (p == bufPtr.value) { + report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone); + bufPtr.value = nextLF(raw, bufPtr.value); + return; + } - ptr = ptrout.value; - if (raw[ptr++] == '\n') - return ptr; - return -1; + p = bufPtr.value; + if (raw[p] == '\n') { + bufPtr.value = p + 1; + } else { + report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone); + bufPtr.value = nextLF(raw, p); + } } /** @@ -309,41 +461,31 @@ public void checkCommit(byte[] raw) throws CorruptObjectException { */ public void checkCommit(@Nullable AnyObjectId id, byte[] raw) throws CorruptObjectException { - int ptr = 0; + bufPtr.value = 0; - if ((ptr = match(raw, ptr, tree)) < 0) - throw new CorruptObjectException( - JGitText.get().corruptObjectNotreeHeader); - if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n') - throw new CorruptObjectException( - JGitText.get().corruptObjectInvalidTree); + if (!match(raw, tree)) { + report(MISSING_TREE, id, JGitText.get().corruptObjectNotreeHeader); + } else if (!checkId(raw)) { + report(BAD_TREE_SHA1, id, JGitText.get().corruptObjectInvalidTree); + } - while (match(raw, ptr, parent) >= 0) { - ptr += parent.length; - if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n') - throw new CorruptObjectException( + while (match(raw, parent)) { + if (!checkId(raw)) { + report(BAD_PARENT_SHA1, id, JGitText.get().corruptObjectInvalidParent); + } } - int p = match(raw, ptr, author); - if (p > ptr) { - if ((ptr = personIdent(raw, p, id)) < 0) { - throw new CorruptObjectException( - JGitText.get().corruptObjectInvalidAuthor); - } - } else if (!skip(id)) { - throw new CorruptObjectException( - JGitText.get().corruptObjectNoAuthor); + if (match(raw, author)) { + checkPersonIdent(raw, id); + } else { + report(MISSING_AUTHOR, id, JGitText.get().corruptObjectNoAuthor); } - p = match(raw, ptr, committer); - if (p > ptr) { - if ((ptr = personIdent(raw, p, id)) < 0) { - throw new CorruptObjectException( - JGitText.get().corruptObjectInvalidCommitter); - } - } else if (!skip(id)) { - throw new CorruptObjectException( + if (match(raw, committer)) { + checkPersonIdent(raw, id); + } else { + report(MISSING_COMMITTER, id, JGitText.get().corruptObjectNoCommitter); } } @@ -373,30 +515,29 @@ public void checkTag(byte[] raw) throws CorruptObjectException { */ public void checkTag(@Nullable AnyObjectId id, byte[] raw) throws CorruptObjectException { - int ptr = 0; - - if ((ptr = match(raw, ptr, object)) < 0) - throw new CorruptObjectException( + bufPtr.value = 0; + if (!match(raw, object)) { + report(MISSING_OBJECT, id, JGitText.get().corruptObjectNoObjectHeader); - if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n') - throw new CorruptObjectException( + } else if (!checkId(raw)) { + report(BAD_OBJECT_SHA1, id, JGitText.get().corruptObjectInvalidObject); + } - if ((ptr = match(raw, ptr, type)) < 0) - throw new CorruptObjectException( + if (!match(raw, type)) { + report(MISSING_TYPE_ENTRY, id, JGitText.get().corruptObjectNoTypeHeader); - ptr = nextLF(raw, ptr); + } + bufPtr.value = nextLF(raw, bufPtr.value); - if (match(raw, ptr, tag) < 0 && !skip(id)) - throw new CorruptObjectException( + if (!match(raw, tag)) { + report(MISSING_TAG_ENTRY, id, JGitText.get().corruptObjectNoTagHeader); - ptr = nextLF(raw, ptr); + } + bufPtr.value = nextLF(raw, bufPtr.value); - if ((ptr = match(raw, ptr, tagger)) > 0) { - if ((ptr = personIdent(raw, ptr, id)) < 0) { - throw new CorruptObjectException( - JGitText.get().corruptObjectInvalidTagger); - } + if (match(raw, tagger)) { + checkPersonIdent(raw, id); } } @@ -485,82 +626,96 @@ public void checkTree(@Nullable AnyObjectId id, byte[] raw) final int sz = raw.length; int ptr = 0; int lastNameB = 0, lastNameE = 0, lastMode = 0; - boolean skip = skip(id); - Set normalized = !skip && (windows || macosx) + Set normalized = windows || macosx ? new HashSet() : null; while (ptr < sz) { int thisMode = 0; for (;;) { - if (ptr == sz) + if (ptr == sz) { throw new CorruptObjectException( JGitText.get().corruptObjectTruncatedInMode); + } final byte c = raw[ptr++]; if (' ' == c) break; - if (c < '0' || c > '7') + if (c < '0' || c > '7') { throw new CorruptObjectException( JGitText.get().corruptObjectInvalidModeChar); - if (thisMode == 0 && c == '0' && !allowZeroMode && !skip) - throw new CorruptObjectException( + } + if (thisMode == 0 && c == '0') { + report(ZERO_PADDED_FILEMODE, id, JGitText.get().corruptObjectInvalidModeStartsZero); + } thisMode <<= 3; thisMode += c - '0'; } - if (FileMode.fromBits(thisMode).getObjectType() == Constants.OBJ_BAD) + if (FileMode.fromBits(thisMode).getObjectType() == OBJ_BAD) { throw new CorruptObjectException(MessageFormat.format( JGitText.get().corruptObjectInvalidMode2, Integer.valueOf(thisMode))); + } final int thisNameB = ptr; - ptr = scanPathSegment(raw, ptr, sz); - if (ptr == sz || raw[ptr] != 0) + ptr = scanPathSegment(raw, ptr, sz, id); + if (ptr == sz || raw[ptr] != 0) { throw new CorruptObjectException( JGitText.get().corruptObjectTruncatedInName); - checkPathSegment2(raw, thisNameB, ptr, skip); + } + checkPathSegment2(raw, thisNameB, ptr, id); if (normalized != null) { - if (!normalized.add(normalize(raw, thisNameB, ptr))) - throw new CorruptObjectException( + if (!normalized.add(normalize(raw, thisNameB, ptr))) { + report(DUPLICATE_ENTRIES, id, JGitText.get().corruptObjectDuplicateEntryNames); - } else if (!skip && duplicateName(raw, thisNameB, ptr)) - throw new CorruptObjectException( + } + } else if (duplicateName(raw, thisNameB, ptr)) { + report(DUPLICATE_ENTRIES, id, JGitText.get().corruptObjectDuplicateEntryNames); + } - if (!skip && lastNameB != 0) { + if (lastNameB != 0) { final int cmp = pathCompare(raw, lastNameB, lastNameE, lastMode, thisNameB, ptr, thisMode); - if (cmp > 0) - throw new CorruptObjectException( + if (cmp > 0) { + report(TREE_NOT_SORTED, id, JGitText.get().corruptObjectIncorrectSorting); + } } lastNameB = thisNameB; lastNameE = ptr; lastMode = thisMode; - ptr += 1 + Constants.OBJECT_ID_LENGTH; - if (ptr > sz) + ptr += 1 + OBJECT_ID_LENGTH; + if (ptr > sz) { throw new CorruptObjectException( JGitText.get().corruptObjectTruncatedInObjectId); + } + if (ObjectId.zeroId().compareTo(raw, ptr - OBJECT_ID_LENGTH) == 0) { + report(NULL_SHA1, id, JGitText.get().corruptObjectZeroId); + } } } - private int scanPathSegment(byte[] raw, int ptr, int end) - throws CorruptObjectException { + private int scanPathSegment(byte[] raw, int ptr, int end, + @Nullable AnyObjectId id) throws CorruptObjectException { for (; ptr < end; ptr++) { byte c = raw[ptr]; - if (c == 0) + if (c == 0) { return ptr; - if (c == '/') - throw new CorruptObjectException( + } + if (c == '/') { + report(FULL_PATHNAME, id, JGitText.get().corruptObjectNameContainsSlash); + } if (windows && isInvalidOnWindows(c)) { - if (c > 31) + if (c > 31) { throw new CorruptObjectException(String.format( JGitText.get().corruptObjectNameContainsChar, Byte.valueOf(c))); + } throw new CorruptObjectException(String.format( JGitText.get().corruptObjectNameContainsByte, Integer.valueOf(c & 0xff))); @@ -578,8 +733,15 @@ private ObjectId idFor(int objType, byte[] raw) { return null; } - private boolean skip(@Nullable AnyObjectId id) { - return skipList != null && id != null && skipList.contains(id); + private void report(@NonNull ErrorType err, @Nullable AnyObjectId id, + String why) throws CorruptObjectException { + if (errors.contains(err) + && (id == null || skipList == null || !skipList.contains(id))) { + if (id != null) { + throw new CorruptObjectException(err, id, why); + } + throw new CorruptObjectException(why); + } } /** @@ -632,75 +794,82 @@ public void checkPath(byte[] raw, int ptr, int end) */ public void checkPathSegment(byte[] raw, int ptr, int end) throws CorruptObjectException { - int e = scanPathSegment(raw, ptr, end); + int e = scanPathSegment(raw, ptr, end, null); if (e < end && raw[e] == 0) throw new CorruptObjectException( JGitText.get().corruptObjectNameContainsNullByte); - checkPathSegment2(raw, ptr, end, false); + checkPathSegment2(raw, ptr, end, null); } - private void checkPathSegment2(byte[] raw, int ptr, int end, boolean skip) - throws CorruptObjectException { - if (ptr == end) - throw new CorruptObjectException( - JGitText.get().corruptObjectNameZeroLength); + private void checkPathSegment2(byte[] raw, int ptr, int end, + @Nullable AnyObjectId id) throws CorruptObjectException { + if (ptr == end) { + report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength); + return; + } + if (raw[ptr] == '.') { switch (end - ptr) { case 1: - throw new CorruptObjectException( - JGitText.get().corruptObjectNameDot); + report(HAS_DOT, id, JGitText.get().corruptObjectNameDot); + break; case 2: - if (raw[ptr + 1] == '.') - throw new CorruptObjectException( + if (raw[ptr + 1] == '.') { + report(HAS_DOTDOT, id, JGitText.get().corruptObjectNameDotDot); + } break; case 4: - if (!skip && isGit(raw, ptr + 1)) - throw new CorruptObjectException(String.format( + if (isGit(raw, ptr + 1)) { + report(HAS_DOTGIT, id, String.format( JGitText.get().corruptObjectInvalidName, RawParseUtils.decode(raw, ptr, end))); + } break; default: - if (!skip && end - ptr > 4 - && isNormalizedGit(raw, ptr + 1, end)) - throw new CorruptObjectException(String.format( + if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) { + report(HAS_DOTGIT, id, String.format( JGitText.get().corruptObjectInvalidName, RawParseUtils.decode(raw, ptr, end))); + } } - } else if (!skip && isGitTilde1(raw, ptr, end)) { - throw new CorruptObjectException(String.format( + } else if (isGitTilde1(raw, ptr, end)) { + report(HAS_DOTGIT, id, String.format( JGitText.get().corruptObjectInvalidName, RawParseUtils.decode(raw, ptr, end))); } - if (!skip) { - if (macosx && isMacHFSGit(raw, ptr, end)) - throw new CorruptObjectException(String.format( - JGitText.get().corruptObjectInvalidNameIgnorableUnicode, - RawParseUtils.decode(raw, ptr, end))); + if (macosx && isMacHFSGit(raw, ptr, end, id)) { + report(HAS_DOTGIT, id, String.format( + JGitText.get().corruptObjectInvalidNameIgnorableUnicode, + RawParseUtils.decode(raw, ptr, end))); + } - if (windows) { - // Windows ignores space and dot at end of file name. - if (raw[end - 1] == ' ' || raw[end - 1] == '.') - throw new CorruptObjectException(String.format( - JGitText.get().corruptObjectInvalidNameEnd, - Character.valueOf(((char) raw[end - 1])))); - if (end - ptr >= 3) - checkNotWindowsDevice(raw, ptr, end); + if (windows) { + // Windows ignores space and dot at end of file name. + if (raw[end - 1] == ' ' || raw[end - 1] == '.') { + report(WIN32_BAD_NAME, id, String.format( + JGitText.get().corruptObjectInvalidNameEnd, + Character.valueOf(((char) raw[end - 1])))); + } + if (end - ptr >= 3) { + checkNotWindowsDevice(raw, ptr, end, id); } } } // Mac's HFS+ folds permutations of ".git" and Unicode ignorable characters // to ".git" therefore we should prevent such names - private static boolean isMacHFSGit(byte[] raw, int ptr, int end) - throws CorruptObjectException { + private boolean isMacHFSGit(byte[] raw, int ptr, int end, + @Nullable AnyObjectId id) throws CorruptObjectException { boolean ignorable = false; byte[] git = new byte[] { '.', 'g', 'i', 't' }; int g = 0; while (ptr < end) { switch (raw[ptr]) { case (byte) 0xe2: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192 - checkTruncatedIgnorableUTF8(raw, ptr, end); + if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) { + return false; + } switch (raw[ptr + 1]) { case (byte) 0x80: switch (raw[ptr + 2]) { @@ -737,7 +906,9 @@ private static boolean isMacHFSGit(byte[] raw, int ptr, int end) return false; } case (byte) 0xef: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65024 - checkTruncatedIgnorableUTF8(raw, ptr, end); + if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) { + return false; + } // U+FEFF 0xefbbbf ZERO WIDTH NO-BREAK SPACE if ((raw[ptr + 1] == (byte) 0xbb) && (raw[ptr + 2] == (byte) 0xbf)) { @@ -758,12 +929,15 @@ private static boolean isMacHFSGit(byte[] raw, int ptr, int end) return false; } - private static void checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end) - throws CorruptObjectException { - if ((ptr + 2) >= end) - throw new CorruptObjectException(MessageFormat.format( + private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end, + @Nullable AnyObjectId id) throws CorruptObjectException { + if ((ptr + 2) >= end) { + report(BAD_UTF8, id, MessageFormat.format( JGitText.get().corruptObjectInvalidNameInvalidUtf8, toHexString(raw, ptr, end))); + return false; + } + return true; } private static String toHexString(byte[] raw, int ptr, int end) { @@ -773,33 +947,36 @@ private static String toHexString(byte[] raw, int ptr, int end) { return b.toString(); } - private static void checkNotWindowsDevice(byte[] raw, int ptr, int end) - throws CorruptObjectException { + private void checkNotWindowsDevice(byte[] raw, int ptr, int end, + @Nullable AnyObjectId id) throws CorruptObjectException { switch (toLower(raw[ptr])) { case 'a': // AUX if (end - ptr >= 3 && toLower(raw[ptr + 1]) == 'u' && toLower(raw[ptr + 2]) == 'x' - && (end - ptr == 3 || raw[ptr + 3] == '.')) - throw new CorruptObjectException( + && (end - ptr == 3 || raw[ptr + 3] == '.')) { + report(WIN32_BAD_NAME, id, JGitText.get().corruptObjectInvalidNameAux); + } break; case 'c': // CON, COM[1-9] if (end - ptr >= 3 && toLower(raw[ptr + 2]) == 'n' && toLower(raw[ptr + 1]) == 'o' - && (end - ptr == 3 || raw[ptr + 3] == '.')) - throw new CorruptObjectException( + && (end - ptr == 3 || raw[ptr + 3] == '.')) { + report(WIN32_BAD_NAME, id, JGitText.get().corruptObjectInvalidNameCon); + } if (end - ptr >= 4 && toLower(raw[ptr + 2]) == 'm' && toLower(raw[ptr + 1]) == 'o' && isPositiveDigit(raw[ptr + 3]) - && (end - ptr == 4 || raw[ptr + 4] == '.')) - throw new CorruptObjectException(String.format( + && (end - ptr == 4 || raw[ptr + 4] == '.')) { + report(WIN32_BAD_NAME, id, String.format( JGitText.get().corruptObjectInvalidNameCom, Character.valueOf(((char) raw[ptr + 3])))); + } break; case 'l': // LPT[1-9] @@ -807,28 +984,31 @@ && isPositiveDigit(raw[ptr + 3]) && toLower(raw[ptr + 1]) == 'p' && toLower(raw[ptr + 2]) == 't' && isPositiveDigit(raw[ptr + 3]) - && (end - ptr == 4 || raw[ptr + 4] == '.')) - throw new CorruptObjectException(String.format( + && (end - ptr == 4 || raw[ptr + 4] == '.')) { + report(WIN32_BAD_NAME, id, String.format( JGitText.get().corruptObjectInvalidNameLpt, Character.valueOf(((char) raw[ptr + 3])))); + } break; case 'n': // NUL if (end - ptr >= 3 && toLower(raw[ptr + 1]) == 'u' && toLower(raw[ptr + 2]) == 'l' - && (end - ptr == 3 || raw[ptr + 3] == '.')) - throw new CorruptObjectException( + && (end - ptr == 3 || raw[ptr + 3] == '.')) { + report(WIN32_BAD_NAME, id, JGitText.get().corruptObjectInvalidNameNul); + } break; case 'p': // PRN if (end - ptr >= 3 && toLower(raw[ptr + 1]) == 'r' && toLower(raw[ptr + 2]) == 'n' - && (end - ptr == 3 || raw[ptr + 3] == '.')) - throw new CorruptObjectException( + && (end - ptr == 3 || raw[ptr + 3] == '.')) { + report(WIN32_BAD_NAME, id, JGitText.get().corruptObjectInvalidNamePrn); + } break; } } @@ -881,6 +1061,15 @@ else if (raw[p] == ' ') return false; } + private boolean match(byte[] b, byte[] src) { + int r = RawParseUtils.match(b, bufPtr.value, src); + if (r < 0) { + return false; + } + bufPtr.value = r; + return true; + } + private static char toLower(byte b) { if ('A' <= b && b <= 'Z') return (char) (b + ('a' - 'A')); 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 42816bd68..b96fe885e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java @@ -1051,6 +1051,9 @@ private void verifySafeObject(final AnyObjectId id, final int type, try { objCheck.check(id, type, data); } catch (CorruptObjectException e) { + if (e.getErrorType() != null) { + throw e; + } throw new CorruptObjectException(MessageFormat.format( JGitText.get().invalidObject, Constants.typeString(type), diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java index 2128f1f7e..72c9c8b93 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java @@ -43,7 +43,11 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase; +import static org.eclipse.jgit.util.StringUtils.toLowerCase; + import java.io.File; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; @@ -62,6 +66,8 @@ * parameters. */ public class TransferConfig { + private static final String FSCK = "fsck"; //$NON-NLS-1$ + /** Key for {@link Config#get(SectionParser)}. */ public static final Config.SectionParser KEY = new SectionParser() { public TransferConfig parse(final Config cfg) { @@ -69,10 +75,14 @@ public TransferConfig parse(final Config cfg) { } }; + enum FsckMode { + ERROR, WARN, IGNORE; + } + private final boolean fetchFsck; private final boolean receiveFsck; private final String fsckSkipList; - private final boolean allowLeadingZeroFileMode; + private final EnumSet ignore; private final boolean allowInvalidPersonIdent; private final boolean safeForWindows; private final boolean safeForMacOS; @@ -88,14 +98,44 @@ public TransferConfig parse(final Config cfg) { boolean fsck = rc.getBoolean("transfer", "fsckobjects", false); //$NON-NLS-1$ //$NON-NLS-2$ fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$ - fsckSkipList = rc.getString("fsck", null, "skipList"); //$NON-NLS-1$ //$NON-NLS-2$ - allowLeadingZeroFileMode = rc.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$ - allowInvalidPersonIdent = rc.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$ - safeForWindows = rc.getBoolean("fsck", "safeForWindows", //$NON-NLS-1$ //$NON-NLS-2$ + fsckSkipList = rc.getString(FSCK, null, "skipList"); //$NON-NLS-1$ + allowInvalidPersonIdent = rc.getBoolean(FSCK, "allowInvalidPersonIdent", false); //$NON-NLS-1$ + safeForWindows = rc.getBoolean(FSCK, "safeForWindows", //$NON-NLS-1$ SystemReader.getInstance().isWindows()); - safeForMacOS = rc.getBoolean("fsck", "safeForMacOS", //$NON-NLS-1$ //$NON-NLS-2$ + safeForMacOS = rc.getBoolean(FSCK, "safeForMacOS", //$NON-NLS-1$ SystemReader.getInstance().isMacOS()); + ignore = EnumSet.noneOf(ObjectChecker.ErrorType.class); + EnumSet set = EnumSet + .noneOf(ObjectChecker.ErrorType.class); + for (String key : rc.getNames(FSCK)) { + if (equalsIgnoreCase(key, "skipList") //$NON-NLS-1$ + || equalsIgnoreCase(key, "allowLeadingZeroFileMode") //$NON-NLS-1$ + || equalsIgnoreCase(key, "allowInvalidPersonIdent") //$NON-NLS-1$ + || equalsIgnoreCase(key, "safeForWindows") //$NON-NLS-1$ + || equalsIgnoreCase(key, "safeForMacOS")) { //$NON-NLS-1$ + continue; + } + + ObjectChecker.ErrorType id = FsckKeyNameHolder.parse(key); + if (id != null) { + switch (rc.getEnum(FSCK, null, key, FsckMode.ERROR)) { + case ERROR: + ignore.remove(id); + break; + case WARN: + case IGNORE: + ignore.add(id); + break; + } + set.add(id); + } + } + if (!set.contains(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE) + && rc.getBoolean(FSCK, "allowLeadingZeroFileMode", false)) { //$NON-NLS-1$ + ignore.add(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE); + } + allowTipSha1InWant = rc.getBoolean( "uploadpack", "allowtipsha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$ allowReachableSha1InWant = rc.getBoolean( @@ -128,7 +168,7 @@ private ObjectChecker newObjectChecker(boolean check) { return null; } return new ObjectChecker() - .setAllowLeadingZeroFileMode(allowLeadingZeroFileMode) + .setIgnore(ignore) .setAllowInvalidPersonIdent(allowInvalidPersonIdent) .setSafeForWindows(safeForWindows) .setSafeForMacOS(safeForMacOS) @@ -188,4 +228,34 @@ private boolean prefixMatch(String p, String s) { } }; } + + static class FsckKeyNameHolder { + private static final Map errors; + + static { + errors = new HashMap<>(); + for (ObjectChecker.ErrorType m : ObjectChecker.ErrorType.values()) { + errors.put(keyNameFor(m.name()), m); + } + } + + @Nullable + static ObjectChecker.ErrorType parse(String key) { + return errors.get(toLowerCase(key)); + } + + private static String keyNameFor(String name) { + StringBuilder r = new StringBuilder(name.length()); + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (c != '_') { + r.append(c); + } + } + return toLowerCase(r.toString()); + } + + private FsckKeyNameHolder() { + } + } } From 02ade82b345ea976d12c32547fb097e726fa44fb Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Thu, 31 Dec 2015 00:48:07 +0100 Subject: [PATCH 45/89] Make sure tests don't blindly continue if a command is "silently" failed Make the default execute() function fail fast on first command printed "fatal: " to output. Introduced executeUnchecked() for few tests which wanted to test fatal output. Change-Id: I5b09aad9443515636811fc4d00bf8b8b9587a626 Signed-off-by: Andrey Loskutov --- .../jgit/lib/CLIRepositoryTestCase.java | 51 +++++++++++++++++-- .../tst/org/eclipse/jgit/pgm/AddTest.java | 2 +- .../tst/org/eclipse/jgit/pgm/ArchiveTest.java | 3 +- .../tst/org/eclipse/jgit/pgm/BranchTest.java | 2 +- .../org/eclipse/jgit/pgm/CheckoutTest.java | 4 +- .../tst/org/eclipse/jgit/pgm/CommitTest.java | 12 ----- .../org/eclipse/jgit/pgm/DescribeTest.java | 6 +-- .../tst/org/eclipse/jgit/pgm/MergeTest.java | 4 +- .../tst/org/eclipse/jgit/pgm/RepoTest.java | 2 +- .../tst/org/eclipse/jgit/pgm/TagTest.java | 2 +- 10 files changed, 59 insertions(+), 29 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java index 4bf9b43cb..a72af9a1c 100644 --- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java +++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java @@ -48,11 +48,13 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.pgm.CLIGitCommand; +import org.eclipse.jgit.pgm.Die; import org.junit.Before; public class CLIRepositoryTestCase extends LocalDiskRepositoryTestCase { @@ -79,7 +81,7 @@ public void setUp() throws Exception { * @return command output * @throws Exception */ - protected String[] execute(String... cmds) throws Exception { + protected String[] executeUnchecked(String... cmds) throws Exception { List result = new ArrayList(cmds.length); for (String cmd : cmds) { result.addAll(CLIGitCommand.execute(cmd, db)); @@ -87,6 +89,28 @@ protected String[] execute(String... cmds) throws Exception { return result.toArray(new String[0]); } + /** + * Executes specified git commands (with arguments), throws exception and + * stops execution on first command which output contains a 'fatal:' error + * + * @param cmds + * each string argument must be a valid git command line, e.g. + * "git branch -h" + * @return command output + * @throws Exception + */ + protected String[] execute(String... cmds) throws Exception { + List result = new ArrayList(cmds.length); + for (String cmd : cmds) { + List out = CLIGitCommand.execute(cmd, db); + if (contains(out, "fatal: ")) { + throw new Die(toString(out)); + } + result.addAll(out); + } + return result.toArray(new String[0]); + } + /** * @param link * the path of the symbolic link to create @@ -196,15 +220,32 @@ protected void assertStringArrayEquals(String expected, String[] actual) { } protected void assertArrayOfLinesEquals(String[] expected, String[] actual) { - assertEquals(toText(expected), toText(actual)); + assertEquals(toString(expected), toString(actual)); } - private static String toText(String[] lines) { + public static String toString(String[] lines) { + return toString(Arrays.asList(lines)); + } + + public static String toString(List lines) { StringBuilder b = new StringBuilder(); for (String s : lines) { - b.append(s); - b.append('\n'); + if (s != null && !s.isEmpty()) { + b.append(s); + if (!s.endsWith("\n")) { + b.append('\n'); + } + } } return b.toString(); } + + public static boolean contains(List lines, String str) { + for (String s : lines) { + if (s.contains(str)) { + return true; + } + } + return false; + } } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java index ca3f00f49..597091369 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java @@ -65,7 +65,7 @@ public void setUp() throws Exception { @Test public void testAddNothing() throws Exception { assertEquals("fatal: Argument \"filepattern\" is required", // - execute("git add")[0]); + executeUnchecked("git add")[0]); } @Test diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java index 9aca2d823..2e02c762b 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java @@ -102,7 +102,8 @@ public void testEmptyTar() throws Exception { @Test public void testUnrecognizedFormat() throws Exception { String[] expect = new String[] { "fatal: Unknown archive format 'nonsense'" }; - String[] actual = execute("git archive --format=nonsense " + emptyTree); + String[] actual = executeUnchecked( + "git archive --format=nonsense " + emptyTree); assertArrayEquals(expect, actual); } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java index 4200cd05c..f369577ba 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java @@ -89,6 +89,6 @@ public void testListContains() throws Exception { @Test public void testExistingBranch() throws Exception { assertEquals("fatal: A branch named 'master' already exists.", - execute("git branch master")[0]); + executeUnchecked("git branch master")[0]); } } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java index 167d8ab51..6175b6c06 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java @@ -109,13 +109,13 @@ public void testCheckoutNewBranchThatAlreadyExists() throws Exception { assertStringArrayEquals( "fatal: A branch named 'master' already exists.", - execute("git checkout -b master")); + executeUnchecked("git checkout -b master")); } @Test public void testCheckoutNewBranchOnBranchToBeBorn() throws Exception { assertStringArrayEquals("fatal: You are on a branch yet to be born", - execute("git checkout -b side")); + executeUnchecked("git checkout -b side")); } @Test diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java index 360ee5a6e..721ed1569 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java @@ -98,16 +98,4 @@ public void testCommitAll() throws Exception { result.trim().equals("On branch master")); } - String toString(String[] arr) { - StringBuilder sb = new StringBuilder(); - for (String s : arr) { - if (s != null && !s.isEmpty()) { - sb.append(s); - if (!s.endsWith("\n")) { - sb.append('\n'); - } - } - } - return sb.toString(); - } } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java index 1c8ba0c2f..a50b243ee 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java @@ -73,7 +73,7 @@ private void initialCommitAndTag() throws Exception { public void testNoHead() throws Exception { assertArrayEquals( new String[] { "fatal: No names found, cannot describe anything." }, - execute("git describe")); + executeUnchecked("git describe")); } @Test @@ -81,7 +81,7 @@ public void testHeadNoTag() throws Exception { git.commit().setMessage("initial commit").call(); assertArrayEquals( new String[] { "fatal: No names found, cannot describe anything." }, - execute("git describe")); + executeUnchecked("git describe")); } @Test @@ -119,7 +119,7 @@ public void testHelpArgumentBeforeUnknown() throws Exception { @Test public void testHelpArgumentAfterUnknown() throws Exception { - String[] output = execute("git describe -XYZ -h"); + String[] output = executeUnchecked("git describe -XYZ -h"); String all = Arrays.toString(output); assertTrue("Unexpected help output: " + all, all.contains("jgit describe")); diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java index 975e8c4f7..641e59b04 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java @@ -195,7 +195,7 @@ public void testNoFastForward() throws Exception { @Test public void testNoFastForwardAndSquash() throws Exception { assertEquals("fatal: You cannot combine --squash with --no-ff.", - execute("git merge master --no-ff --squash")[0]); + executeUnchecked("git merge master --no-ff --squash")[0]); } @Test @@ -210,7 +210,7 @@ public void testFastForwardOnly() throws Exception { git.commit().setMessage("commit#2").call(); assertEquals("fatal: Not possible to fast-forward, aborting.", - execute("git merge master --ff-only")[0]); + executeUnchecked("git merge master --ff-only")[0]); } @Test diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java index 85fc1dbb4..715682755 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java @@ -103,7 +103,7 @@ public void setUp() throws Exception { @Test public void testMissingPath() throws Exception { assertEquals("fatal: Argument \"path\" is required", - execute("git repo")[0]); + executeUnchecked("git repo")[0]); } /** diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/TagTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/TagTest.java index ab09db5a5..0fe25f550 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/TagTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/TagTest.java @@ -68,6 +68,6 @@ public void testTagTwice() throws Exception { git.commit().setMessage("commit").call(); assertEquals("fatal: tag 'test' already exists", - execute("git tag test")[0]); + executeUnchecked("git tag test")[0]); } } From 53c3bbe42f58e99ac352021f9fbfd79f9becf156 Mon Sep 17 00:00:00 2001 From: Eryk Szymanski Date: Wed, 17 Jun 2015 17:17:17 +0200 Subject: [PATCH 46/89] Fix encoding problem from curl repostory on github Pushing curl repository to gerrit fails with a message: remote: error: internal error while processing changes java.nio.charset.IllegalCharsetNameException: 'utf8' curl repository url: https://github.com/bagder/curl.git To avoid this problem encodingAliases in RawParseUtils have been extended to contain "'utf8'" (single quoted utf8) string. Change-Id: I40f613cfdcabf0dc9455bee45116ab8d8c7dd6ee Signed-off-by: Eryk Szymanski --- org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java index 45c339fb4..a20e0b060 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java @@ -83,6 +83,7 @@ public final class RawParseUtils { static { encodingAliases = new HashMap(); encodingAliases.put("latin-1", Charset.forName("ISO-8859-1")); //$NON-NLS-1$ //$NON-NLS-2$ + encodingAliases.put("'utf8'", Charset.forName("UTF-8")); //$NON-NLS-1$ //$NON-NLS-2$ digits10 = new byte['9' + 1]; Arrays.fill(digits10, (byte) -1); From 13502fef8f4814c6c5bdfa63674c94f9d32b5531 Mon Sep 17 00:00:00 2001 From: David Ostrovsky Date: Fri, 4 Dec 2015 08:27:57 +0100 Subject: [PATCH 47/89] Implement Buck driven build Today there are plenty of modern build tool systems available in the wild (in no particular order): * http://bazel.io * https://pantsbuild.github.io * http://shakebuild.com * https://ninja-build.org * https://buckbuild.com The attributes, that all these build tools have in common, are: * reliable * correct * very fast * reproducible It must not always be the other build tool, this project is currently using. Or, quoting Gerrit Code Review maintainer here: "Friends, don't let friends use !" This change is non-complete implementation of JGit build in Buck, needed by Gerrit Code Review to replace its dependency with standlone JGit cell. This is very useful when a developer is working on both projects and is trying to integrate changes made in JGit in Gerrit. The supported workflow is: $ cd jgit $ emacs $ cd ../gerrit $ buck build --config repositories.jgit=../jgit gerrit With --config repositories.jgit=../jgit jgit cell is routed through JGit development tree. To build jgit, issue: $ buck build //:jgit [-] PROCESSING BUCK FILES...FINISHED 0,0s Yes, you can't measure no-op build time, given that Buck daemon is used. Change-Id: I301a71b19fba35a5093d8cc64d4ba970c2877a44 Signed-off-by: David Ostrovsky --- .buckconfig | 15 ++++++++ .gitignore | 2 + BUCK | 38 +++++++++++++++++++ lib/BUCK | 62 +++++++++++++++++++++++++++++++ org.eclipse.jgit.archive/BUCK | 13 +++++++ org.eclipse.jgit.http.server/BUCK | 10 +++++ org.eclipse.jgit.junit/BUCK | 10 +++++ org.eclipse.jgit/BUCK | 20 ++++++++++ tools/default.defs | 42 +++++++++++++++++++++ 9 files changed, 212 insertions(+) create mode 100644 .buckconfig create mode 100644 BUCK create mode 100644 lib/BUCK create mode 100644 org.eclipse.jgit.archive/BUCK create mode 100644 org.eclipse.jgit.http.server/BUCK create mode 100644 org.eclipse.jgit.junit/BUCK create mode 100644 org.eclipse.jgit/BUCK create mode 100644 tools/default.defs diff --git a/.buckconfig b/.buckconfig new file mode 100644 index 000000000..ef82fa07f --- /dev/null +++ b/.buckconfig @@ -0,0 +1,15 @@ +[buildfile] + includes = //tools/default.defs + +[java] + src_roots = src, resources + +[project] + ignore = .git + +[cache] + mode = dir + +[download] + maven_repo = http://repo1.maven.org/maven2 + in_build = true diff --git a/.gitignore b/.gitignore index 139e5aee6..6c6219948 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target /.project +/buck-cache +/buck-out diff --git a/BUCK b/BUCK new file mode 100644 index 000000000..7704281ec --- /dev/null +++ b/BUCK @@ -0,0 +1,38 @@ +java_library( + name = 'jgit', + exported_deps = ['//org.eclipse.jgit:jgit'], + visibility = ['PUBLIC'], +) + +java_library( + name = 'jgit_src', + exported_deps = ['//org.eclipse.jgit:jgit_src'], + visibility = ['PUBLIC'], +) + +java_library( + name = 'jgit-servlet', + exported_deps = [ + ':jgit', + '//org.eclipse.jgit.http.server:jgit-servlet' + ], + visibility = ['PUBLIC'], +) + +java_library( + name = 'jgit-archive', + exported_deps = [ + ':jgit', + '//org.eclipse.jgit.archive:jgit-archive' + ], + visibility = ['PUBLIC'], +) + +java_library( + name = 'junit', + exported_deps = [ + ':jgit', + '//org.eclipse.jgit.junit:junit' + ], + visibility = ['PUBLIC'], +) diff --git a/lib/BUCK b/lib/BUCK new file mode 100644 index 000000000..19cc2e039 --- /dev/null +++ b/lib/BUCK @@ -0,0 +1,62 @@ +maven_jar( + name = 'jsch', + bin_sha1 = '658b682d5c817b27ae795637dfec047c63d29935', + src_sha1 = '791359d94d6edcace686a56d0727ee093a2f7c33', + group = 'com.jcraft', + artifact = 'jsch', + version = '0.1.53', +) + +maven_jar( + name = 'javaewah', + bin_sha1 = 'eceaf316a8faf0e794296ebe158ae110c7d72a5a', + src_sha1 = 'a50d78eb630e05439461f3130b94b3bcd1ea6f03', + group = 'com.googlecode.javaewah', + artifact = 'JavaEWAH', + version = '0.7.9', +) + +maven_jar( + name = 'httpcomponents', + bin_sha1 = '4c47155e3e6c9a41a28db36680b828ced53b8af4', + src_sha1 = 'af4d76be0c46ee26b0d9d1d4a34d244a633cac84', + group = 'org.apache.httpcomponents', + artifact = 'httpclient', + version = '4.3.6', +) + +maven_jar( + name = 'slf4j-api', + bin_sha1 = '0081d61b7f33ebeab314e07de0cc596f8e858d97', + src_sha1 = '58d38f68d4a867d4552ae27960bb348d7eaa1297', + group = 'org.slf4j', + artifact = 'slf4j-api', + version = '1.7.2', +) + +maven_jar( + name = 'servlet-api', + bin_sha1 = '3cd63d075497751784b2fa84be59432f4905bf7c', + src_sha1 = 'ab3976d4574c48d22dc1abf6a9e8bd0fdf928223', + group = 'javax.servlet', + artifact = 'javax.servlet-api', + version = '3.1.0', +) + +maven_jar( + name = 'commons-compress', + bin_sha1 = 'c7d9b580aff9e9f1998361f16578e63e5c064699', + src_sha1 = '396b81bdfd0fb617178e1707ef64832215307c78', + group = 'org.apache.commons', + artifact = 'commons-compress', + version = '1.6', +) + +maven_jar( + name = 'junit', + bin_sha1 = '4e031bb61df09069aeb2bffb4019e7a5034a4ee0', + src_sha1 = '28e0ad201304e4a4abf999ca0570b7cffc352c3c', + group = 'junit', + artifact = 'junit', + version = '4.11', +) diff --git a/org.eclipse.jgit.archive/BUCK b/org.eclipse.jgit.archive/BUCK new file mode 100644 index 000000000..ae170324e --- /dev/null +++ b/org.eclipse.jgit.archive/BUCK @@ -0,0 +1,13 @@ +java_library( + name = 'jgit-archive', + srcs = glob( + ['src/**'], + excludes = ['src/org/eclipse/jgit/archive/FormatActivator.java'], + ), + resources = glob(['resources/**']), + provided_deps = [ + '//org.eclipse.jgit:jgit', + '//lib:commons-compress', + ], + visibility = ['PUBLIC'], +) diff --git a/org.eclipse.jgit.http.server/BUCK b/org.eclipse.jgit.http.server/BUCK new file mode 100644 index 000000000..3743557aa --- /dev/null +++ b/org.eclipse.jgit.http.server/BUCK @@ -0,0 +1,10 @@ +java_library( + name = 'jgit-servlet', + srcs = glob(['src/**']), + resources = glob(['resources/**']), + provided_deps = [ + '//org.eclipse.jgit:jgit', + '//lib:servlet-api', + ], + visibility = ['PUBLIC'], +) diff --git a/org.eclipse.jgit.junit/BUCK b/org.eclipse.jgit.junit/BUCK new file mode 100644 index 000000000..7e2543220 --- /dev/null +++ b/org.eclipse.jgit.junit/BUCK @@ -0,0 +1,10 @@ +java_library( + name = 'junit', + srcs = glob(['src/**']), + resources = glob(['resources/**']), + provided_deps = [ + '//org.eclipse.jgit:jgit', + '//lib:junit', + ], + visibility = ['PUBLIC'], +) diff --git a/org.eclipse.jgit/BUCK b/org.eclipse.jgit/BUCK new file mode 100644 index 000000000..73e208057 --- /dev/null +++ b/org.eclipse.jgit/BUCK @@ -0,0 +1,20 @@ +SRCS = glob(['src/**']) +RESOURCES = glob(['resources/**']) + +java_library( + name = 'jgit', + srcs = SRCS, + resources = RESOURCES, + deps = [ + '//lib:javaewah', + '//lib:jsch', + '//lib:httpcomponents', + '//lib:slf4j-api', + ], + visibility = ['PUBLIC'], +) + +java_sources( + name = 'jgit_src', + srcs = SRCS + RESOURCES, +) diff --git a/tools/default.defs b/tools/default.defs new file mode 100644 index 000000000..3481fa1f8 --- /dev/null +++ b/tools/default.defs @@ -0,0 +1,42 @@ +def java_sources( + name, + srcs, + visibility = ['PUBLIC'] + ): + java_library( + name = name, + resources = srcs, + visibility = visibility, + ) + +def maven_jar( + name, + group, + artifact, + version, + bin_sha1, + src_sha1, + visibility = ['PUBLIC']): + jar_name = '%s__jar' % name + src_name = '%s__src' % name + + remote_file( + name = jar_name, + sha1 = bin_sha1, + url = 'mvn:%s:%s:jar:%s' % (group, artifact, version), + out = '%s.jar' % jar_name, + ) + + remote_file( + name = src_name, + sha1 = src_sha1, + url = 'mvn:%s:%s:src:%s' % (group, artifact, version), + out = '%s.jar' % src_name, + ) + + prebuilt_jar( + name = name, + binary_jar = ':' + jar_name, + source_jar = ':' + src_name, + visibility = visibility) + From 34de70a5d42fc89275c9108351cf2e8154ff3322 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Thu, 31 Dec 2015 11:18:02 -0800 Subject: [PATCH 48/89] buck: build standalone jgit binary Construct the java_application JAR wrapped with the shell script header. This is enough to clone a repository over HTTPs: $ buck build :jgit_bin $ buck-out/gen/jgit_bin/jgit_bin clone https://... Change-Id: I4aceb4e77b2ec9be76a32ec93d94f2dafe9acce6 --- BUCK | 6 +++++ lib/BUCK | 36 +++++++++++++++++++++++++ org.eclipse.jgit.http.apache/BUCK | 12 +++++++++ org.eclipse.jgit.pgm/BUCK | 44 +++++++++++++++++++++++++++++++ org.eclipse.jgit.ui/BUCK | 7 +++++ 5 files changed, 105 insertions(+) create mode 100644 org.eclipse.jgit.http.apache/BUCK create mode 100644 org.eclipse.jgit.pgm/BUCK create mode 100644 org.eclipse.jgit.ui/BUCK diff --git a/BUCK b/BUCK index 7704281ec..fca0e8a7f 100644 --- a/BUCK +++ b/BUCK @@ -36,3 +36,9 @@ java_library( ], visibility = ['PUBLIC'], ) + +genrule( + name = 'jgit_bin', + cmd = 'ln -s $(location //org.eclipse.jgit.pgm:jgit) $OUT', + out = 'jgit_bin', +) diff --git a/lib/BUCK b/lib/BUCK index 19cc2e039..f33cadbbe 100644 --- a/lib/BUCK +++ b/lib/BUCK @@ -25,6 +25,24 @@ maven_jar( version = '4.3.6', ) +maven_jar( + name = 'httpcore', + bin_sha1 = 'f91b7a4aadc5cf486df6e4634748d7dd7a73f06d', + src_sha1 = '1b0aa62a6a91e9fa00c16f0a4a2c874804ed3b1e', + group = 'org.apache.httpcomponents', + artifact = 'httpcore', + version = '4.3.3', +) + +maven_jar( + name = 'commons-logging', + bin_sha1 = 'f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f', + src_sha1 = '28bb0405fddaf04f15058fbfbe01fe2780d7d3b6', + group = 'commons-logging', + artifact = 'commons-logging', + version = '1.1.3', +) + maven_jar( name = 'slf4j-api', bin_sha1 = '0081d61b7f33ebeab314e07de0cc596f8e858d97', @@ -34,6 +52,15 @@ maven_jar( version = '1.7.2', ) +maven_jar( + name = 'slf4j-simple', + bin_sha1 = '760055906d7353ba4f7ce1b8908bc6b2e91f39fa', + src_sha1 = '09474919128b3a7fcf21a5f9c907f5251f234544', + group = 'org.slf4j', + artifact = 'slf4j-simple', + version = '1.7.2', +) + maven_jar( name = 'servlet-api', bin_sha1 = '3cd63d075497751784b2fa84be59432f4905bf7c', @@ -52,6 +79,15 @@ maven_jar( version = '1.6', ) +maven_jar( + name = 'args4j', + bin_sha1 = '139441471327b9cc6d56436cb2a31e60eb6ed2ba', + src_sha1 = '22631b78cc8f60a6918557e8cbdb33e90f63a77f', + group = 'args4j', + artifact = 'args4j', + version = '2.0.15', +) + maven_jar( name = 'junit', bin_sha1 = '4e031bb61df09069aeb2bffb4019e7a5034a4ee0', diff --git a/org.eclipse.jgit.http.apache/BUCK b/org.eclipse.jgit.http.apache/BUCK new file mode 100644 index 000000000..f48f33a1a --- /dev/null +++ b/org.eclipse.jgit.http.apache/BUCK @@ -0,0 +1,12 @@ +java_library( + name = 'http-apache', + srcs = glob(['src/**']), + resources = glob(['resources/**']), + deps = [ + '//org.eclipse.jgit:jgit', + '//lib:commons-logging', + '//lib:httpcomponents', + '//lib:httpcore', + ], + visibility = ['PUBLIC'], +) diff --git a/org.eclipse.jgit.pgm/BUCK b/org.eclipse.jgit.pgm/BUCK new file mode 100644 index 000000000..64237c32e --- /dev/null +++ b/org.eclipse.jgit.pgm/BUCK @@ -0,0 +1,44 @@ +java_library( + name = 'pgm', + srcs = glob(['src/**']), + resources = glob(['resources/**']), + deps = [ + ':services', + '//org.eclipse.jgit:jgit', + '//org.eclipse.jgit.archive:jgit-archive', + '//org.eclipse.jgit.http.apache:http-apache', + '//org.eclipse.jgit.ui:ui', + '//lib:args4j', + ], + visibility = ['PUBLIC'], +) + +prebuilt_jar( + name = 'services', + binary_jar = ':services__jar', +) + +genrule( + name = 'services__jar', + cmd = 'cd $SRCDIR ; zip -qr $OUT .', + srcs = glob(['META-INF/services/*']), + out = 'services.jar', +) + +genrule( + name = 'jgit', + cmd = 'cat $SRCDIR/jgit.sh $(location :jgit_jar) >$OUT;' + + 'chmod a+x $OUT', + srcs = ['jgit.sh'], + out = 'jgit', + visibility = ['PUBLIC'], +) + +java_binary( + name = 'jgit_jar', + main_class = 'org.eclipse.jgit.pgm.Main', + deps = [ + ':pgm', + '//lib:slf4j-simple', + ], +) diff --git a/org.eclipse.jgit.ui/BUCK b/org.eclipse.jgit.ui/BUCK new file mode 100644 index 000000000..fcd87cf9a --- /dev/null +++ b/org.eclipse.jgit.ui/BUCK @@ -0,0 +1,7 @@ +java_library( + name = 'ui', + srcs = glob(['src/**']), + resources = glob(['resources/**']), + deps = ['//org.eclipse.jgit:jgit'], + visibility = ['PUBLIC'], +) From 2f8d787b5f691bd55584fb74556d408301ddb788 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Thu, 31 Dec 2015 10:44:30 -0800 Subject: [PATCH 49/89] buck: run tests Compile each test in its own java_test() target so they can run in parallel, reducing total time spent testing on large machines. $ buck test --all [-] PROCESSING BUCK FILES...FINISHED 0.3s [100%] [-] BUILDING...FINISHED 2.9s [100%] (351/383 JOBS, 351 UPDATED, 0.0% CACHE MISS) [-] TESTING...FINISHED 98.1s (3360 PASS/15 SKIP/0 FAIL) Change-Id: I8d6541268315089299f933ed23d785b1b3431133 --- .buckconfig | 2 +- lib/BUCK | 27 ++++++++++ org.eclipse.jgit.pgm.test/BUCK | 37 +++++++++++++ org.eclipse.jgit.pgm/BUCK | 1 + org.eclipse.jgit.test/BUCK | 94 ++++++++++++++++++++++++++++++++++ 5 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 org.eclipse.jgit.pgm.test/BUCK create mode 100644 org.eclipse.jgit.test/BUCK diff --git a/.buckconfig b/.buckconfig index ef82fa07f..b2e07accc 100644 --- a/.buckconfig +++ b/.buckconfig @@ -2,7 +2,7 @@ includes = //tools/default.defs [java] - src_roots = src, resources + src_roots = src, resources, tst [project] ignore = .git diff --git a/lib/BUCK b/lib/BUCK index f33cadbbe..524612bde 100644 --- a/lib/BUCK +++ b/lib/BUCK @@ -79,6 +79,15 @@ maven_jar( version = '1.6', ) +maven_jar( + name = 'tukaani-xz', + bin_sha1 = '66db21c8484120cb6a51b5b3ea47b6f383942bec', + src_sha1 = '6396220725701d767c553902c41120d7bf38e9f5', + group = 'org.tukaani', + artifact = 'xz', + version = '1.3', +) + maven_jar( name = 'args4j', bin_sha1 = '139441471327b9cc6d56436cb2a31e60eb6ed2ba', @@ -96,3 +105,21 @@ maven_jar( artifact = 'junit', version = '4.11', ) + +maven_jar( + name = 'hamcrest-library', + bin_sha1 = '4785a3c21320980282f9f33d0d1264a69040538f', + src_sha1 = '047a7ee46628ab7133129cd7cef1e92657bc275e', + group = 'org.hamcrest', + artifact = 'hamcrest-library', + version = '1.3', +) + +maven_jar( + name = 'hamcrest-core', + bin_sha1 = '42a25dc3219429f0e5d060061f71acb49bf010a0', + src_sha1 = '1dc37250fbc78e23a65a67fbbaf71d2e9cbc3c0b', + group = 'org.hamcrest', + artifact = 'hamcrest-core', + version = '1.3', +) diff --git a/org.eclipse.jgit.pgm.test/BUCK b/org.eclipse.jgit.pgm.test/BUCK new file mode 100644 index 000000000..dae9cd924 --- /dev/null +++ b/org.eclipse.jgit.pgm.test/BUCK @@ -0,0 +1,37 @@ +TESTS = glob(['tst/**/*.java']) + +for t in TESTS: + n = t[len('tst/'):len(t)-len('.java')].replace('/', '.') + java_test( + name = n, + labels = ['pgm'], + srcs = [t], + deps = [ + ':helpers', + '//org.eclipse.jgit:jgit', + '//org.eclipse.jgit.archive:jgit-archive', + '//org.eclipse.jgit.junit:junit', + '//org.eclipse.jgit.pgm:pgm', + '//lib:hamcrest-core', + '//lib:hamcrest-library', + '//lib:javaewah', + '//lib:junit', + '//lib:slf4j-api', + '//lib:slf4j-simple', + '//lib:commons-compress', + '//lib:tukaani-xz', + ], + source_under_test = ['//org.eclipse.jgit.pgm:pgm'], + ) + +java_library( + name = 'helpers', + srcs = glob(['src/**/*.java']), + deps = [ + '//org.eclipse.jgit:jgit', + '//org.eclipse.jgit.pgm:pgm', + '//org.eclipse.jgit.junit:junit', + '//lib:args4j', + '//lib:junit', + ], +) diff --git a/org.eclipse.jgit.pgm/BUCK b/org.eclipse.jgit.pgm/BUCK index 64237c32e..d99c39d03 100644 --- a/org.eclipse.jgit.pgm/BUCK +++ b/org.eclipse.jgit.pgm/BUCK @@ -40,5 +40,6 @@ java_binary( deps = [ ':pgm', '//lib:slf4j-simple', + '//lib:tukaani-xz', ], ) diff --git a/org.eclipse.jgit.test/BUCK b/org.eclipse.jgit.test/BUCK new file mode 100644 index 000000000..9a9d9efd0 --- /dev/null +++ b/org.eclipse.jgit.test/BUCK @@ -0,0 +1,94 @@ +PKG = 'tst/org/eclipse/jgit/' +HELPERS = glob(['src/**/*.java']) + [PKG + c for c in [ + 'api/AbstractRemoteCommandTest.java', + 'diff/AbstractDiffTestCase.java', + 'internal/storage/file/GcTestCase.java', + 'internal/storage/file/PackIndexTestCase.java', + 'internal/storage/file/XInputStream.java', + 'nls/GermanTranslatedBundle.java', + 'nls/MissingPropertyBundle.java', + 'nls/NoPropertiesBundle.java', + 'nls/NonTranslatedBundle.java', + 'revwalk/RevQueueTestCase.java', + 'revwalk/RevWalkTestCase.java', + 'transport/SpiTransport.java', + 'treewalk/FileTreeIteratorWithTimeControl.java', + 'treewalk/filter/AlwaysCloneTreeFilter.java', + 'test/resources/SampleDataRepositoryTestCase.java', + 'util/CPUTimeStopWatch.java', + 'util/io/Strings.java', +]] + +DATA = [ + PKG + 'lib/empty.gitindex.dat', + PKG + 'lib/sorttest.gitindex.dat', +] + +TESTS = glob( + ['tst/**/*.java'], + excludes = HELPERS + DATA, +) + +DEPS = { + PKG + 'nls/RootLocaleTest.java': [ + '//org.eclipse.jgit.pgm:pgm', + '//org.eclipse.jgit.ui:ui', + ], +} + +for src in TESTS: + name = src[len('tst/'):len(src)-len('.java')].replace('/', '.') + labels = [] + if name.startswith('org.eclipse.jgit.'): + l = name[len('org.eclipse.jgit.'):] + if l.startswith('internal.storage.'): + l = l[len('internal.storage.'):] + i = l.find('.') + if i > 0: + labels.append(l[:i]) + else: + labels.append(i) + if 'lib' not in labels: + labels.append('lib') + + java_test( + name = name, + labels = labels, + srcs = [src], + deps = [ + ':helpers', + ':tst_rsrc', + '//org.eclipse.jgit:jgit', + '//org.eclipse.jgit.junit:junit', + '//lib:hamcrest-core', + '//lib:hamcrest-library', + '//lib:javaewah', + '//lib:junit', + '//lib:slf4j-api', + '//lib:slf4j-simple', + ] + DEPS.get(src, []), + source_under_test = ['//org.eclipse.jgit:jgit'], + ) + +java_library( + name = 'helpers', + srcs = HELPERS, + resources = DATA, + deps = [ + '//org.eclipse.jgit:jgit', + '//org.eclipse.jgit.junit:junit', + '//lib:junit', + ], +) + +prebuilt_jar( + name = 'tst_rsrc', + binary_jar = ':tst_rsrc_jar', +) + +genrule( + name = 'tst_rsrc_jar', + cmd = 'cd $SRCDIR/tst-rsrc ; zip -qr $OUT .', + srcs = glob(['tst-rsrc/**']), + out = 'tst_rsrc.jar', +) From c426a964ed41001731288d4fb8c26bdda2cfe169 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Thu, 31 Dec 2015 16:03:13 -0800 Subject: [PATCH 50/89] clone: display progress messages Also support -q/--quiet flag to disable progress. Change-Id: I979277502c990f6dec052d095461c996ff8fe577 --- .../src/org/eclipse/jgit/pgm/Clone.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java index cd6953cb0..04078287f 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java @@ -50,6 +50,7 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.SystemReader; @@ -70,6 +71,9 @@ class Clone extends AbstractFetchCommand { @Option(name = "--bare", usage = "usage_bareClone") private boolean isBare; + @Option(name = "--quiet", usage = "usage_quiet") + private Boolean quiet; + @Argument(index = 0, required = true, metaVar = "metaVar_uriish") private String sourceUri; @@ -109,10 +113,16 @@ protected void run() throws Exception { command.setGitDir(gitdir == null ? null : new File(gitdir)); command.setDirectory(localNameF); - outw.println(MessageFormat.format(CLIText.get().cloningInto, localName)); + boolean msgs = quiet == null || !quiet.booleanValue(); + if (msgs) { + command.setProgressMonitor(new TextProgressMonitor(errw)); + outw.println(MessageFormat.format( + CLIText.get().cloningInto, localName)); + outw.flush(); + } try { db = command.call().getRepository(); - if (db.resolve(Constants.HEAD) == null) + if (msgs && db.resolve(Constants.HEAD) == null) outw.println(CLIText.get().clonedEmptyRepository); } catch (InvalidRemoteException e) { throw die(MessageFormat.format(CLIText.get().doesNotExist, @@ -121,8 +131,9 @@ protected void run() throws Exception { if (db != null) db.close(); } - - outw.println(); - outw.flush(); + if (msgs) { + outw.println(); + outw.flush(); + } } } From 09500165a8592386c92c1f10a36bfd4fc4666a11 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Thu, 31 Dec 2015 16:12:51 -0800 Subject: [PATCH 51/89] Fix "remote: Counting objects: ..." formatting Trailing whitespace is usually removed in properties files so JGitText did not supply a space between : and the remote message. Ensure the space exists at runtime by reading the localized string and appending a space if it is missing. Messages should be dynamically fetched and not held in a static class variable, as they can be changed using thread locals. Change-Id: If6a3707d64094253b1a5304fbfafcf195db7497a --- .../jgit/transport/SideBandInputStream.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java index cf388e271..fe9f2a315 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java @@ -78,12 +78,8 @@ * @see SideBandOutputStream */ class SideBandInputStream extends InputStream { - private static final String PFX_REMOTE = JGitText.get().prefixRemote; - static final int CH_DATA = 1; - static final int CH_PROGRESS = 2; - static final int CH_ERROR = 3; private static Pattern P_UNBOUNDED = Pattern @@ -174,7 +170,7 @@ private void needDataPacket() throws IOException { continue; case CH_ERROR: eof = true; - throw new TransportException(PFX_REMOTE + readString(available)); + throw new TransportException(remote(readString(available))); default: throw new PackProtocolException( MessageFormat.format(JGitText.get().invalidChannel, @@ -241,7 +237,18 @@ private void doProgressLine(final String msg) throws IOException { } private void beginTask(final int totalWorkUnits) { - monitor.beginTask(PFX_REMOTE + currentTask, totalWorkUnits); + monitor.beginTask(remote(currentTask), totalWorkUnits); + } + + private static String remote(String msg) { + String prefix = JGitText.get().prefixRemote; + StringBuilder r = new StringBuilder(prefix.length() + msg.length() + 1); + r.append(prefix); + if (prefix.length() > 0 && prefix.charAt(prefix.length() - 1) != ' ') { + r.append(' '); + } + r.append(msg); + return r.toString(); } private String readString(final int len) throws IOException { From 887debc52767bd53ba07af56142fdc1265d3f4fc Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Thu, 31 Dec 2015 20:07:03 -0800 Subject: [PATCH 52/89] buck: pin to stable version Like with Gerrit, pin JGit to a single version of Buck that is known to work with current Buck files and JUnit tests. Notably a more recent version of Buck used by Gerrit (01a0c54d827) fails WalkEncryptionTest. Change-Id: I6b94c332e4bde97a1910f48cf12eb8698f97d540 --- .buckversion | 1 + 1 file changed, 1 insertion(+) create mode 100644 .buckversion diff --git a/.buckversion b/.buckversion new file mode 100644 index 000000000..9daac2cea --- /dev/null +++ b/.buckversion @@ -0,0 +1 @@ +1b03b4313b91b634bd604fc3487a05f877e59dee From da3174a812d07b03dd0738f572e187412911e925 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 1 Jan 2016 11:04:11 -0800 Subject: [PATCH 53/89] buck: set vm_args for tests Maven pom files force the local encoding to UTF-8 to ensure there are no differences between machines. They also set the JVM max heap to 256m. Match both in Buck so that results are consistent. Change-Id: Ice5476dd09352a444a0c97aa0dc28806fddf2ab4 --- org.eclipse.jgit.pgm.test/BUCK | 1 + org.eclipse.jgit.test/BUCK | 1 + 2 files changed, 2 insertions(+) diff --git a/org.eclipse.jgit.pgm.test/BUCK b/org.eclipse.jgit.pgm.test/BUCK index dae9cd924..a3859c9b4 100644 --- a/org.eclipse.jgit.pgm.test/BUCK +++ b/org.eclipse.jgit.pgm.test/BUCK @@ -22,6 +22,7 @@ for t in TESTS: '//lib:tukaani-xz', ], source_under_test = ['//org.eclipse.jgit.pgm:pgm'], + vm_args = ['-Xmx256m', '-Dfile.encoding=UTF-8'], ) java_library( diff --git a/org.eclipse.jgit.test/BUCK b/org.eclipse.jgit.test/BUCK index 9a9d9efd0..3df3336b4 100644 --- a/org.eclipse.jgit.test/BUCK +++ b/org.eclipse.jgit.test/BUCK @@ -68,6 +68,7 @@ for src in TESTS: '//lib:slf4j-simple', ] + DEPS.get(src, []), source_under_test = ['//org.eclipse.jgit:jgit'], + vm_args = ['-Xmx256m', '-Dfile.encoding=UTF-8'], ) java_library( From f1b79c3c89ca2ddf080c1af35039ca7e604e5b61 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Fri, 1 Jan 2016 23:54:15 +0100 Subject: [PATCH 54/89] buck: run http tests Running tests using buck reveals that HttpClientTests are broken and weren't executed by Maven since these test classes don't match the maven-surefire-plugin's default for test classes **/*Test.java. Will be fixed in a follow-up change. Change-Id: I82a01b5fd3f0a930bec2423a29a256601dadc248 Signed-off-by: Matthias Sohn --- lib/jetty/BUCK | 56 ++++++++++++++++++++++++++++++++ org.eclipse.jgit.http.test/BUCK | 40 +++++++++++++++++++++++ org.eclipse.jgit.junit.http/BUCK | 18 ++++++++++ 3 files changed, 114 insertions(+) create mode 100644 lib/jetty/BUCK create mode 100644 org.eclipse.jgit.http.test/BUCK create mode 100644 org.eclipse.jgit.junit.http/BUCK diff --git a/lib/jetty/BUCK b/lib/jetty/BUCK new file mode 100644 index 000000000..6e7dec306 --- /dev/null +++ b/lib/jetty/BUCK @@ -0,0 +1,56 @@ +VERSION = '9.2.13.v20150730' +GROUP = 'org.eclipse.jetty' + +maven_jar( + name = 'servlet', + bin_sha1 = '5ad6e38015a97ae9a60b6c2ad744ccfa9cf93a50', + src_sha1 = '78fbec19321150552d91f9e079c2f2ca33222b01', + group = GROUP, + artifact = 'jetty-servlet', + version = VERSION, +) + +maven_jar( + name = 'security', + bin_sha1 = 'cc7c7f27ec4cc279253be1675d9e47e58b995943', + src_sha1 = '75632ebdf8bd651faafb97106c92496db59e165d', + group = GROUP, + artifact = 'jetty-security', + version = VERSION, +) + +maven_jar( + name = 'server', + bin_sha1 = '5be7d1da0a7abffd142de3091d160717c120b6ab', + src_sha1 = '203e123f83efe2a5b8a9c74854c7897fe3563302', + group = GROUP, + artifact = 'jetty-server', + version = VERSION, +) + +maven_jar( + name = 'http', + bin_sha1 = '23a745d9177ef67ef53cc46b9b70c5870082efc2', + src_sha1 = '5f87f7ff2057cd4b0995bc4fffe17b2aff64c130', + group = GROUP, + artifact = 'jetty-http', + version = VERSION, +) + +maven_jar( + name = 'io', + bin_sha1 = '7a351e6a1b63dfd56b6632623f7ca2793ffb67ad', + src_sha1 = 'bbd61a84b748fc295456e1c5c3070aaf40a68f62', + group = GROUP, + artifact = 'jetty-io', + version = VERSION, +) + +maven_jar( + name = 'util', + bin_sha1 = 'c101476360a7cdd0670462de04053507d5e70c97', + src_sha1 = '15ceecce141971b4e0facb861b3d10120ad6ce03', + group = GROUP, + artifact = 'jetty-util', + version = VERSION, +) diff --git a/org.eclipse.jgit.http.test/BUCK b/org.eclipse.jgit.http.test/BUCK new file mode 100644 index 000000000..d2ced7a24 --- /dev/null +++ b/org.eclipse.jgit.http.test/BUCK @@ -0,0 +1,40 @@ +TESTS = glob(['tst/**/*.java']) + +for t in TESTS: + n = t[len('tst/'):len(t)-len('.java')].replace('/', '.') + java_test( + name = n, + labels = ['http'], + srcs = [t], + deps = [ + ':helpers', + '//org.eclipse.jgit:jgit', + '//org.eclipse.jgit.http.apache:http-apache', + '//org.eclipse.jgit.http.server:jgit-servlet', + '//org.eclipse.jgit.junit:junit', + '//org.eclipse.jgit.junit.http:junit-http', + '//lib:hamcrest-core', + '//lib:hamcrest-library', + '//lib:junit', + '//lib:servlet-api', + '//lib/jetty:http', + '//lib/jetty:io', + '//lib/jetty:server', + '//lib/jetty:servlet', + '//lib/jetty:security', + '//lib/jetty:util', + ], + source_under_test = ['//org.eclipse.jgit.http.server:jgit-servlet'], + ) + +java_library( + name = 'helpers', + srcs = glob(['src/**/*.java']), + deps = [ + '//org.eclipse.jgit:jgit', + '//org.eclipse.jgit.http.server:jgit-servlet', + '//org.eclipse.jgit.junit:junit', + '//org.eclipse.jgit.junit.http:junit-http', + '//lib:junit', + ], +) diff --git a/org.eclipse.jgit.junit.http/BUCK b/org.eclipse.jgit.junit.http/BUCK new file mode 100644 index 000000000..68976a68a --- /dev/null +++ b/org.eclipse.jgit.junit.http/BUCK @@ -0,0 +1,18 @@ +java_library( + name = 'junit-http', + srcs = glob(['src/**']), + resources = glob(['resources/**']), + provided_deps = [ + '//org.eclipse.jgit:jgit', + '//org.eclipse.jgit.http.server:jgit-servlet', + '//org.eclipse.jgit.junit:junit', + '//lib:junit', + '//lib:servlet-api', + '//lib/jetty:http', + '//lib/jetty:server', + '//lib/jetty:servlet', + '//lib/jetty:security', + '//lib/jetty:util', + ], + visibility = ['PUBLIC'], +) From 80bf52dfe4de77b3e4960e52628c8075b91e2bb1 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Sat, 2 Jan 2016 02:09:16 +0100 Subject: [PATCH 55/89] Ensure all http tests are run and fix broken tests HttpClientTests were broken. This wasn't discovered since maven-surefire-plugin's by default only executes test classes matching **/*Test.java. Fix this by also including **/*.Tests.java and fix the failing tests. Change-Id: I487a30fb333de993a9f8d8fff491d3b0e7fb02cc Signed-off-by: Matthias Sohn --- org.eclipse.jgit.http.test/pom.xml | 4 ++++ .../tst/org/eclipse/jgit/http/test/HttpClientTests.java | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml index dd52a89e6..0af30f659 100644 --- a/org.eclipse.jgit.http.test/pom.xml +++ b/org.eclipse.jgit.http.test/pom.xml @@ -134,6 +134,10 @@ maven-surefire-plugin -Djava.io.tmpdir=${project.build.directory} -Xmx300m + + **/*Test.java + **/*Tests.java + diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java index 6fb130231..cf20898b3 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java @@ -303,7 +303,7 @@ public void testListRemote_Dumb_NeedsAuth() throws Exception { fail("connection opened even info/refs needs auth basic"); } catch (TransportException err) { String exp = dumbAuthBasicURI + ": " - + JGitText.get().notAuthorized; + + JGitText.get().noCredentialsProvider; assertEquals(exp, err.getMessage()); } } finally { @@ -347,7 +347,7 @@ public void testListRemote_Smart_UploadPackNeedsAuth() throws Exception { fail("connection opened even though service disabled"); } catch (TransportException err) { String exp = smartAuthBasicURI + ": " - + JGitText.get().notAuthorized; + + JGitText.get().noCredentialsProvider; assertEquals(exp, err.getMessage()); } } finally { @@ -369,7 +369,8 @@ public void testListRemote_Smart_UploadPackDisabled() throws Exception { t.openFetch(); fail("connection opened even though service disabled"); } catch (TransportException err) { - String exp = smartAuthNoneURI + ": Git access forbidden"; + String exp = smartAuthNoneURI + ": " + + JGitText.get().serviceNotEnabledNoName; assertEquals(exp, err.getMessage()); } } finally { From 35db319e8a10a26f08bad3f66ebf6f51008d9735 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Sat, 2 Jan 2016 17:32:11 +0100 Subject: [PATCH 56/89] Fixed few locale dependent pgm tests See https://dev.eclipse.org/mhonarc/lists/jgit-dev/msg03040.html Change-Id: If51f3c750684d82cb6443f1578636c9f5ca56e2b Signed-off-by: Andrey Loskutov --- ...se.jgit.pgm--All-Tests (Java8) (de).launch | 30 ++++++++++++++++++ .../tst/org/eclipse/jgit/pgm/AddTest.java | 9 ++++-- .../tst/org/eclipse/jgit/pgm/RepoTest.java | 10 ++++-- ....jgit.core--All-Tests (Java 8) (de).launch | 31 +++++++++++++++++++ 4 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java8) (de).launch create mode 100644 org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 8) (de).launch diff --git a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java8) (de).launch b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java8) (de).launch new file mode 100644 index 000000000..5c137f28f --- /dev/null +++ b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java8) (de).launch @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java index 597091369..3edd9b88e 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/AddTest.java @@ -45,6 +45,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.dircache.DirCache; @@ -64,8 +65,12 @@ public void setUp() throws Exception { @Test public void testAddNothing() throws Exception { - assertEquals("fatal: Argument \"filepattern\" is required", // - executeUnchecked("git add")[0]); + try { + execute("git add"); + fail("Must die"); + } catch (Die e) { + // expected, requires argument + } } @Test diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java index 715682755..0eee771d2 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RepoTest.java @@ -42,9 +42,9 @@ */ package org.eclipse.jgit.pgm; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.File; import java.util.Arrays; @@ -102,8 +102,12 @@ public void setUp() throws Exception { @Test public void testMissingPath() throws Exception { - assertEquals("fatal: Argument \"path\" is required", - executeUnchecked("git repo")[0]); + try { + execute("git repo"); + fail("Must die"); + } catch (Die e) { + // expected, requires argument + } } /** diff --git a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 8) (de).launch b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 8) (de).launch new file mode 100644 index 000000000..f12a529e1 --- /dev/null +++ b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 8) (de).launch @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d6deb190e61b9b891af2fe67198a18b4ddb2057f Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Sat, 2 Jan 2016 13:24:02 +0100 Subject: [PATCH 57/89] Simplify pgm tests: allow varargs and trim output for toString() Change-Id: Ia5bcd9e560b90cf872fef75c2800c889ef1cc85a Signed-off-by: Andrey Loskutov --- .../jgit/lib/CLIRepositoryTestCase.java | 12 ++++-- .../tst/org/eclipse/jgit/pgm/CommitTest.java | 39 +++++++++---------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java index a72af9a1c..a3436a017 100644 --- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java +++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java @@ -223,20 +223,24 @@ protected void assertArrayOfLinesEquals(String[] expected, String[] actual) { assertEquals(toString(expected), toString(actual)); } - public static String toString(String[] lines) { + public static String toString(String... lines) { return toString(Arrays.asList(lines)); } public static String toString(List lines) { StringBuilder b = new StringBuilder(); for (String s : lines) { + // trim indentation, to simplify tests + s = s.trim(); if (s != null && !s.isEmpty()) { b.append(s); - if (!s.endsWith("\n")) { - b.append('\n'); - } + b.append('\n'); } } + // delete last line break to allow simpler tests with one line compare + if (b.length() > 0 && b.charAt(b.length() - 1) == '\n') { + b.deleteCharAt(b.length() - 1); + } return b.toString(); } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java index 721ed1569..6bccb6d4a 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CommitTest.java @@ -42,7 +42,7 @@ */ package org.eclipse.jgit.pgm; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import org.eclipse.jgit.lib.CLIRepositoryTestCase; import org.junit.Test; @@ -54,26 +54,27 @@ public void testCommitPath() throws Exception { writeTrashFile("a", "a"); writeTrashFile("b", "a"); String result = toString(execute("git add a")); - assertTrue("Unexpected output: " + result, result.isEmpty()); + assertEquals("", result); result = toString(execute("git status -- a")); - assertTrue("Unexpected output: " + result, - result.contains("new file: a")); + assertEquals(toString("On branch master", "Changes to be committed:", + "new file: a"), result); result = toString(execute("git status -- b")); - assertTrue("Unexpected output: " + result, - result.trim().contains("Untracked files:\n b")); + assertEquals(toString("On branch master", "Untracked files:", "b"), + result); result = toString(execute("git commit a -m 'added a'")); - assertTrue("Unexpected output: " + result, result.contains("added a")); + assertEquals( + "[master 8cb3ef7e5171aaee1792df6302a5a0cd30425f7a] added a", + result); result = toString(execute("git status -- a")); - assertTrue("Unexpected output: " + result, - result.trim().equals("On branch master")); + assertEquals("On branch master", result); result = toString(execute("git status -- b")); - assertTrue("Unexpected output: " + result, - result.trim().contains("Untracked files:\n b")); + assertEquals(toString("On branch master", "Untracked files:", "b"), + result); } @Test @@ -81,21 +82,19 @@ public void testCommitAll() throws Exception { writeTrashFile("a", "a"); writeTrashFile("b", "a"); String result = toString(execute("git add a b")); - assertTrue("Unexpected output: " + result, result.isEmpty()); + assertEquals("", result); result = toString(execute("git status -- a b")); - assertTrue("Unexpected output: " + result, - result.contains("new file: a")); - assertTrue("Unexpected output: " + result, - result.contains("new file: b")); + assertEquals(toString("On branch master", "Changes to be committed:", + "new file: a", "new file: b"), result); result = toString(execute("git commit -m 'added a b'")); - assertTrue("Unexpected output: " + result, - result.contains("added a b")); + assertEquals( + "[master 3c93fa8e3a28ee26690498be78016edcb3a38c73] added a b", + result); result = toString(execute("git status -- a b")); - assertTrue("Unexpected output: " + result, - result.trim().equals("On branch master")); + assertEquals("On branch master", result); } } From 63bb0bcd4af3e70afe13173b4e3b9173fcb8eaea Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Mon, 28 Dec 2015 15:47:36 +0100 Subject: [PATCH 58/89] branch command: provide convenient and meaningful options help Added tests for all options, fixed multi-valued options parsing. Bug: 484951 Change-Id: I5558589049544ea6c84932bc01f1f9df09e1f682 Signed-off-by: Andrey Loskutov --- .../tst/org/eclipse/jgit/pgm/BranchTest.java | 184 +++++++++++++++++- .../jgit/pgm/internal/CLIText.properties | 5 + .../src/org/eclipse/jgit/pgm/Branch.java | 130 +++++++++---- .../eclipse/jgit/pgm/internal/CLIText.java | 2 + .../eclipse/jgit/pgm/opt/CmdLineParser.java | 1 + .../pgm/opt/OptionWithValuesListHandler.java | 52 +++++ 6 files changed, 330 insertions(+), 44 deletions(-) create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/OptionWithValuesListHandler.java diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java index f369577ba..74506bc94 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java @@ -43,6 +43,11 @@ package org.eclipse.jgit.pgm; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.CLIRepositoryTestCase; @@ -62,8 +67,9 @@ public void setUp() throws Exception { @Test public void testList() throws Exception { + assertEquals("* master", toString(execute("git branch"))); assertEquals("* master 6fd41be initial commit", - execute("git branch -v")[0]); + toString(execute("git branch -v"))); } @Test @@ -71,8 +77,10 @@ public void testListDetached() throws Exception { RefUpdate updateRef = db.updateRef(Constants.HEAD, true); updateRef.setNewObjectId(db.resolve("6fd41be")); updateRef.update(); - assertEquals("* (no branch) 6fd41be initial commit", - execute("git branch -v")[0]); + assertEquals( + toString("* (no branch) 6fd41be initial commit", + "master 6fd41be initial commit"), + toString(execute("git branch -v"))); } @Test @@ -80,15 +88,175 @@ public void testListContains() throws Exception { new Git(db).branchCreate().setName("initial").call(); RevCommit second = new Git(db).commit().setMessage("second commit") .call(); - assertArrayOfLinesEquals(new String[] { " initial", "* master", "" }, - execute("git branch --contains 6fd41be")); - assertArrayOfLinesEquals(new String[] { "* master", "" }, - execute("git branch --contains " + second.name())); + assertEquals(toString(" initial", "* master"), + toString(execute("git branch --contains 6fd41be"))); + assertEquals("* master", + toString(execute("git branch --contains " + second.name()))); } @Test public void testExistingBranch() throws Exception { assertEquals("fatal: A branch named 'master' already exists.", - executeUnchecked("git branch master")[0]); + toString(executeUnchecked("git branch master"))); + } + + @Test + public void testRenameSingleArg() throws Exception { + try { + toString(execute("git branch -m")); + fail("Must die"); + } catch (Die e) { + // expected, requires argument + } + String result = toString(execute("git branch -m slave")); + assertEquals("", result); + result = toString(execute("git branch -a")); + assertEquals("* slave", result); + } + + @Test + public void testRenameTwoArgs() throws Exception { + String result = toString(execute("git branch -m master slave")); + assertEquals("", result); + result = toString(execute("git branch -a")); + assertEquals("* slave", result); + } + + @Test + public void testCreate() throws Exception { + try { + toString(execute("git branch a b")); + fail("Must die"); + } catch (Die e) { + // expected, too many arguments + } + String result = toString(execute("git branch second")); + assertEquals("", result); + result = toString(execute("git branch")); + assertEquals(toString("* master", "second"), result); + result = toString(execute("git branch -v")); + assertEquals(toString("* master 6fd41be initial commit", + "second 6fd41be initial commit"), result); + } + + @Test + public void testDelete() throws Exception { + try { + toString(execute("git branch -d")); + fail("Must die"); + } catch (Die e) { + // expected, requires argument + } + String result = toString(execute("git branch second")); + assertEquals("", result); + result = toString(execute("git branch -d second")); + assertEquals("", result); + result = toString(execute("git branch")); + assertEquals("* master", result); + } + + @Test + public void testDeleteMultiple() throws Exception { + String result = toString(execute("git branch second", + "git branch third", "git branch fourth")); + assertEquals("", result); + result = toString(execute("git branch -d second third fourth")); + assertEquals("", result); + result = toString(execute("git branch")); + assertEquals("* master", result); + } + + @Test + public void testDeleteForce() throws Exception { + try { + toString(execute("git branch -D")); + fail("Must die"); + } catch (Die e) { + // expected, requires argument + } + String result = toString(execute("git branch second")); + assertEquals("", result); + result = toString(execute("git checkout second")); + assertEquals("Switched to branch 'second'", result); + + File a = writeTrashFile("a", "a"); + assertTrue(a.exists()); + execute("git add a", "git commit -m 'added a'"); + + result = toString(execute("git checkout master")); + assertEquals("Switched to branch 'master'", result); + + result = toString(execute("git branch")); + assertEquals(toString("* master", "second"), result); + + try { + toString(execute("git branch -d second")); + fail("Must die"); + } catch (Die e) { + // expected, the current HEAD is on second and not merged to master + } + result = toString(execute("git branch -D second")); + assertEquals("", result); + + result = toString(execute("git branch")); + assertEquals("* master", result); + } + + @Test + public void testDeleteForceMultiple() throws Exception { + String result = toString(execute("git branch second", + "git branch third", "git branch fourth")); + + assertEquals("", result); + result = toString(execute("git checkout second")); + assertEquals("Switched to branch 'second'", result); + + File a = writeTrashFile("a", "a"); + assertTrue(a.exists()); + execute("git add a", "git commit -m 'added a'"); + + result = toString(execute("git checkout master")); + assertEquals("Switched to branch 'master'", result); + + result = toString(execute("git branch")); + assertEquals(toString("fourth", "* master", "second", "third"), result); + + try { + toString(execute("git branch -d second third fourth")); + fail("Must die"); + } catch (Die e) { + // expected, the current HEAD is on second and not merged to master + } + result = toString(execute("git branch")); + assertEquals(toString("fourth", "* master", "second", "third"), result); + + result = toString(execute("git branch -D second third fourth")); + assertEquals("", result); + + result = toString(execute("git branch")); + assertEquals("* master", result); + } + + @Test + public void testCreateFromOldCommit() throws Exception { + File a = writeTrashFile("a", "a"); + assertTrue(a.exists()); + execute("git add a", "git commit -m 'added a'"); + File b = writeTrashFile("b", "b"); + assertTrue(b.exists()); + execute("git add b", "git commit -m 'added b'"); + String result = toString(execute("git log -n 1 --reverse")); + String firstCommitId = result.substring("commit ".length(), + result.indexOf('\n')); + + result = toString(execute("git branch -f second " + firstCommitId)); + assertEquals("", result); + + result = toString(execute("git branch")); + assertEquals(toString("* master", "second"), result); + + result = toString(execute("git checkout second")); + assertEquals("Switched to branch 'second'", result); + assertFalse(b.exists()); } } diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties index 3dcbda3b9..a24b81794 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties @@ -20,6 +20,7 @@ branchAlreadyExists=A branch named ''{0}'' already exists. branchCreatedFrom=branch: Created from {0} branchDetachedHEAD=detached HEAD branchIsNotAnAncestorOfYourCurrentHEAD=The branch ''{0}'' is not an ancestor of your current HEAD.\nIf you are sure you want to delete it, run ''jgit branch -D {0}''. +branchNameRequired=branch name required branchNotFound=branch ''{0}'' not found. cacheTreePathInfo="{0}": {1} entries, {2} children cannotBeRenamed={0} cannot be renamed @@ -89,7 +90,9 @@ metaVar_author=AUTHOR metaVar_base=base metaVar_blameL=START,END metaVar_blameReverse=START..END +metaVar_branchAndStartPoint=branch [start-name] metaVar_branchName=branch +metaVar_branchNames=branch ... metaVar_bucket=BUCKET metaVar_command=command metaVar_commandDetail=DETAIL @@ -109,6 +112,7 @@ metaVar_message=message metaVar_n=n metaVar_name=name metaVar_object=object +metaVar_oldNewBranchNames=[oldbranch] newbranch metaVar_op=OP metaVar_pass=PASS metaVar_path=path @@ -125,6 +129,7 @@ metaVar_treeish=tree-ish metaVar_uriish=uri-ish metaVar_url=URL metaVar_user=USER +metaVar_values=value ... metaVar_version=VERSION mostCommonlyUsedCommandsAre=The most commonly used commands are: needApprovalToDestroyCurrentRepository=Need approval to destroy current repository diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java index 65aa24f35..045f3571e 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java @@ -45,7 +45,6 @@ import java.io.IOException; import java.text.MessageFormat; -import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; @@ -65,15 +64,18 @@ import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.internal.CLIText; -import org.eclipse.jgit.pgm.opt.CmdLineParser; +import org.eclipse.jgit.pgm.opt.OptionWithValuesListHandler; import org.eclipse.jgit.revwalk.RevWalk; import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.ExampleMode; import org.kohsuke.args4j.Option; @Command(common = true, usage = "usage_listCreateOrDeleteBranches") class Branch extends TextBuiltin { + private String otherBranch; + private boolean createForce; + private boolean rename; + @Option(name = "--remote", aliases = { "-r" }, usage = "usage_actOnRemoteTrackingBranches") private boolean remote = false; @@ -83,23 +85,69 @@ class Branch extends TextBuiltin { @Option(name = "--contains", metaVar = "metaVar_commitish", usage = "usage_printOnlyBranchesThatContainTheCommit") private String containsCommitish; - @Option(name = "--delete", aliases = { "-d" }, usage = "usage_deleteFullyMergedBranch") - private boolean delete = false; + private List delete; - @Option(name = "--delete-force", aliases = { "-D" }, usage = "usage_deleteBranchEvenIfNotMerged") - private boolean deleteForce = false; + @Option(name = "--delete", aliases = { + "-d" }, metaVar = "metaVar_branchNames", usage = "usage_deleteFullyMergedBranch", handler = OptionWithValuesListHandler.class) + public void delete(List names) { + if (names.isEmpty()) { + throw die(CLIText.get().branchNameRequired); + } + delete = names; + } - @Option(name = "--create-force", aliases = { "-f" }, usage = "usage_forceCreateBranchEvenExists") - private boolean createForce = false; + private List deleteForce; - @Option(name = "-m", usage = "usage_moveRenameABranch") - private boolean rename = false; + @Option(name = "--delete-force", aliases = { + "-D" }, metaVar = "metaVar_branchNames", usage = "usage_deleteBranchEvenIfNotMerged", handler = OptionWithValuesListHandler.class) + public void deleteForce(List names) { + if (names.isEmpty()) { + throw die(CLIText.get().branchNameRequired); + } + deleteForce = names; + } + + @Option(name = "--create-force", aliases = { + "-f" }, metaVar = "metaVar_branchAndStartPoint", usage = "usage_forceCreateBranchEvenExists", handler = OptionWithValuesListHandler.class) + public void createForce(List branchAndStartPoint) { + createForce = true; + if (branchAndStartPoint.isEmpty()) { + throw die(CLIText.get().branchNameRequired); + } + if (branchAndStartPoint.size() > 2) { + throw die(CLIText.get().tooManyRefsGiven); + } + if (branchAndStartPoint.size() == 1) { + branch = branchAndStartPoint.get(0); + } else { + branch = branchAndStartPoint.get(0); + otherBranch = branchAndStartPoint.get(1); + } + } + + @Option(name = "--move", aliases = { + "-m" }, metaVar = "metaVar_oldNewBranchNames", usage = "usage_moveRenameABranch", handler = OptionWithValuesListHandler.class) + public void moveRename(List currentAndNew) { + rename = true; + if (currentAndNew.isEmpty()) { + throw die(CLIText.get().branchNameRequired); + } + if (currentAndNew.size() > 2) { + throw die(CLIText.get().tooManyRefsGiven); + } + if (currentAndNew.size() == 1) { + branch = currentAndNew.get(0); + } else { + branch = currentAndNew.get(0); + otherBranch = currentAndNew.get(1); + } + } @Option(name = "--verbose", aliases = { "-v" }, usage = "usage_beVerbose") private boolean verbose = false; - @Argument - private List branches = new ArrayList(); + @Argument(metaVar = "metaVar_name") + private String branch; private final Map printRefs = new LinkedHashMap(); @@ -110,30 +158,33 @@ class Branch extends TextBuiltin { @Override protected void run() throws Exception { - if (delete || deleteForce) - delete(deleteForce); - else { - if (branches.size() > 2) - throw die(CLIText.get().tooManyRefsGiven + new CmdLineParser(this).printExample(ExampleMode.ALL)); - + if (delete != null || deleteForce != null) { + if (delete != null) { + delete(delete, false); + } + if (deleteForce != null) { + delete(deleteForce, true); + } + } else { if (rename) { String src, dst; - if (branches.size() == 1) { + if (otherBranch == null) { final Ref head = db.getRef(Constants.HEAD); - if (head != null && head.isSymbolic()) + if (head != null && head.isSymbolic()) { src = head.getLeaf().getName(); - else + } else { throw die(CLIText.get().cannotRenameDetachedHEAD); - dst = branches.get(0); + } + dst = branch; } else { - src = branches.get(0); + src = branch; final Ref old = db.getRef(src); if (old == null) throw die(MessageFormat.format(CLIText.get().doesNotExist, src)); if (!old.getName().startsWith(Constants.R_HEADS)) throw die(MessageFormat.format(CLIText.get().notABranch, src)); src = old.getName(); - dst = branches.get(1); + dst = otherBranch; } if (!dst.startsWith(Constants.R_HEADS)) @@ -145,13 +196,14 @@ protected void run() throws Exception { if (r.rename() != Result.RENAMED) throw die(MessageFormat.format(CLIText.get().cannotBeRenamed, src)); - } else if (branches.size() > 0) { - String newHead = branches.get(0); + } else if (createForce || branch != null) { + String newHead = branch; String startBranch; - if (branches.size() == 2) - startBranch = branches.get(1); - else + if (createForce) { + startBranch = otherBranch; + } else { startBranch = Constants.HEAD; + } Ref startRef = db.getRef(startBranch); ObjectId startAt = db.resolve(startBranch + "^0"); //$NON-NLS-1$ if (startRef != null) { @@ -164,22 +216,27 @@ protected void run() throws Exception { } startBranch = Repository.shortenRefName(startBranch); String newRefName = newHead; - if (!newRefName.startsWith(Constants.R_HEADS)) + if (!newRefName.startsWith(Constants.R_HEADS)) { newRefName = Constants.R_HEADS + newRefName; - if (!Repository.isValidRefName(newRefName)) + } + if (!Repository.isValidRefName(newRefName)) { throw die(MessageFormat.format(CLIText.get().notAValidRefName, newRefName)); - if (!createForce && db.resolve(newRefName) != null) + } + if (!createForce && db.resolve(newRefName) != null) { throw die(MessageFormat.format(CLIText.get().branchAlreadyExists, newHead)); + } RefUpdate updateRef = db.updateRef(newRefName); updateRef.setNewObjectId(startAt); updateRef.setForceUpdate(createForce); updateRef.setRefLogMessage(MessageFormat.format(CLIText.get().branchCreatedFrom, startBranch), false); Result update = updateRef.update(); - if (update == Result.REJECTED) + if (update == Result.REJECTED) { throw die(MessageFormat.format(CLIText.get().couldNotCreateBranch, newHead, update.toString())); + } } else { - if (verbose) + if (verbose) { rw = new RevWalk(db); + } list(); } } @@ -249,7 +306,8 @@ private void printHead(final ObjectReader reader, final String ref, outw.println(); } - private void delete(boolean force) throws IOException { + private void delete(List branches, boolean force) + throws IOException { String current = db.getBranch(); ObjectId head = db.resolve(Constants.HEAD); for (String branch : branches) { diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java index f5d581ad0..fd86ddf2b 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java @@ -85,6 +85,7 @@ public static String formatLine(String line) { /***/ public String branchCreatedFrom; /***/ public String branchDetachedHEAD; /***/ public String branchIsNotAnAncestorOfYourCurrentHEAD; + /***/ public String branchNameRequired; /***/ public String branchNotFound; /***/ public String cacheTreePathInfo; /***/ public String configFileNotFound; @@ -184,6 +185,7 @@ public static String formatLine(String line) { /***/ public String metaVar_uriish; /***/ public String metaVar_url; /***/ public String metaVar_user; + /***/ public String metaVar_values; /***/ public String metaVar_version; /***/ public String mostCommonlyUsedCommandsAre; /***/ public String needApprovalToDestroyCurrentRepository; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java index 66d481642..a1e39502c 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java @@ -86,6 +86,7 @@ public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser { registerHandler(RefSpec.class, RefSpecHandler.class); registerHandler(RevCommit.class, RevCommitHandler.class); registerHandler(RevTree.class, RevTreeHandler.class); + registerHandler(List.class, OptionWithValuesListHandler.class); } private final Repository db; diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/OptionWithValuesListHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/OptionWithValuesListHandler.java new file mode 100644 index 000000000..3de7a8109 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/OptionWithValuesListHandler.java @@ -0,0 +1,52 @@ +package org.eclipse.jgit.pgm.opt; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.pgm.internal.CLIText; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; + +/** + * Handler which allows to parse option with few values + * + * @since 4.2 + */ +public class OptionWithValuesListHandler extends OptionHandler> { + + /** + * @param parser + * @param option + * @param setter + */ + public OptionWithValuesListHandler(CmdLineParser parser, + OptionDef option, Setter> setter) { + super(parser, option, setter); + } + + @Override + public int parseArguments(Parameters params) throws CmdLineException { + final List list = new ArrayList<>(); + for (int idx = 0; idx < params.size(); idx++) { + final String p; + try { + p = params.getParameter(idx); + } catch (CmdLineException cle) { + break; + } + list.add(p); + } + setter.addValue(list); + return list.size(); + } + + @Override + public String getDefaultMetaVariable() { + return CLIText.get().metaVar_values; + } + +} From 776c3003aa0f6d66f222c2d74d86a59cf9f8fe98 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sun, 3 Jan 2016 09:30:16 -0800 Subject: [PATCH 59/89] buck: set Bundle-Version for :jgit_bin Run git describe during the build to determine the lineage of this working directory and stamp this information into the binary. Change-Id: I0ad24125c31e4280ccf900bac4065924087b05aa --- org.eclipse.jgit.pgm/BUCK | 31 ++++++++++++++++++++++++++++--- tools/git.defs | 9 +++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 tools/git.defs diff --git a/org.eclipse.jgit.pgm/BUCK b/org.eclipse.jgit.pgm/BUCK index d99c39d03..edcf2fc28 100644 --- a/org.eclipse.jgit.pgm/BUCK +++ b/org.eclipse.jgit.pgm/BUCK @@ -1,3 +1,5 @@ +include_defs('//tools/git.defs') + java_library( name = 'pgm', srcs = glob(['src/**']), @@ -27,8 +29,14 @@ genrule( genrule( name = 'jgit', - cmd = 'cat $SRCDIR/jgit.sh $(location :jgit_jar) >$OUT;' + - 'chmod a+x $OUT', + cmd = ''.join([ + 'mkdir $TMP/META-INF &&', + 'cp $(location :binary_manifest) $TMP/META-INF/MANIFEST.MF &&', + 'cp $(location :jgit_jar) $TMP/jgit.jar &&', + 'cd $TMP && zip $TMP/jgit.jar META-INF/MANIFEST.MF &&', + 'cat $SRCDIR/jgit.sh $TMP/jgit.jar >$OUT &&', + 'chmod a+x $OUT', + ]), srcs = ['jgit.sh'], out = 'jgit', visibility = ['PUBLIC'], @@ -36,10 +44,27 @@ genrule( java_binary( name = 'jgit_jar', - main_class = 'org.eclipse.jgit.pgm.Main', deps = [ ':pgm', '//lib:slf4j-simple', '//lib:tukaani-xz', ], + blacklist = [ + 'META-INF/DEPENDENCIES', + 'META-INF/maven/.*', + ], +) + +genrule( + name = 'binary_manifest', + cmd = ';'.join(['echo "%s: %s" >>$OUT' % e for e in [ + ('Manifest-Version', '1.0'), + ('Main-Class', 'org.eclipse.jgit.pgm.Main'), + ('Bundle-Version', git_version()), + ('Implementation-Title', 'JGit Command Line Interface'), + ('Implementation-Vendor', 'Eclipse.org - JGit'), + ('Implementation-Vendor-URL', 'http://www.eclipse.org/jgit/'), + ('Implementation-Vendor-Id', 'org.eclipse.jgit'), + ]] + ['echo >>$OUT']), + out = 'MANIFEST.MF', ) diff --git a/tools/git.defs b/tools/git.defs new file mode 100644 index 000000000..557dff231 --- /dev/null +++ b/tools/git.defs @@ -0,0 +1,9 @@ +def git_version(): + import subprocess + cmd = ['git', 'describe', '--always', '--match', 'v[0-9].*', '--dirty'] + p = subprocess.Popen(cmd, stdout = subprocess.PIPE) + v = p.communicate()[0].strip() + r = p.returncode + if r != 0: + raise subprocess.CalledProcessError(r, ' '.join(cmd)) + return v From 4c574b39b40be466bfe7d6aaaa011bd5a2e9023b Mon Sep 17 00:00:00 2001 From: David Ostrovsky Date: Mon, 4 Jan 2016 00:44:07 +0100 Subject: [PATCH 60/89] buck: Make :jgit_src target work in cross-cell environment This artifact is used from unzip utility in Gerrit Code Review build toolchain and thus the file must exist on the file system. Moreover, trying to use java_binary() didn't work either, as the zip layout was wrong: all files contained 'org.eclipse.jgit/src/' prefix. Change-Id: I00e3269a7a1a6c6d1fe7e60d1bf1c69b8e57d79d Signed-off-by: David Ostrovsky --- BUCK | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BUCK b/BUCK index fca0e8a7f..f19b7bdc5 100644 --- a/BUCK +++ b/BUCK @@ -4,9 +4,10 @@ java_library( visibility = ['PUBLIC'], ) -java_library( +genrule( name = 'jgit_src', - exported_deps = ['//org.eclipse.jgit:jgit_src'], + cmd = 'ln -s $(location //org.eclipse.jgit:jgit_src) $OUT', + out = 'jgit_src.zip', visibility = ['PUBLIC'], ) From f0d634eed764f8ff228ece18c6960375e90e6958 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 6 Jan 2016 11:54:08 -0800 Subject: [PATCH 61/89] DFS: Allow other RefDatabase implementations Permit a DfsRepository implementation to use a different RefDatabase than DfsRefDatabase. Change-Id: Ia263285f547bde1943993cc994d0222185021a16 --- .../jgit/internal/storage/dfs/DfsGarbageCollector.java | 7 +++---- .../jgit/internal/storage/dfs/DfsRefDatabase.java | 5 +++++ .../jgit/internal/storage/dfs/DfsRepository.java | 10 +++++----- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java index bcc46c355..bb51fc4ca 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java @@ -70,6 +70,7 @@ import org.eclipse.jgit.lib.ObjectIdSet; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.pack.PackConfig; import org.eclipse.jgit.storage.pack.PackStatistics; @@ -78,9 +79,7 @@ /** Repack and garbage collect a repository. */ public class DfsGarbageCollector { private final DfsRepository repo; - - private final DfsRefDatabase refdb; - + private final RefDatabase refdb; private final DfsObjDatabase objdb; private final List newPackDesc; @@ -195,7 +194,7 @@ public boolean pack(ProgressMonitor pm) throws IOException { ctx = (DfsReader) objdb.newReader(); try { - refdb.clearCache(); + refdb.refresh(); objdb.clearCache(); refsBefore = refdb.getRefs(ALL); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java index a1035a128..e5469f6b8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java @@ -261,6 +261,11 @@ public void create() { // Nothing to do. } + @Override + public void refresh() { + clearCache(); + } + @Override public void close() { clearCache(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java index 0d5fd0f85..ef8845084 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java @@ -79,9 +79,6 @@ protected DfsRepository(DfsRepositoryBuilder builder) { @Override public abstract DfsObjDatabase getObjectDatabase(); - @Override - public abstract DfsRefDatabase getRefDatabase(); - /** @return a description of this repository. */ public DfsRepositoryDescription getDescription() { return description; @@ -95,7 +92,10 @@ public DfsRepositoryDescription getDescription() { * the repository cannot be checked. */ public boolean exists() throws IOException { - return getRefDatabase().exists(); + if (getRefDatabase() instanceof DfsRefDatabase) { + return ((DfsRefDatabase) getRefDatabase()).exists(); + } + return true; } @Override @@ -117,7 +117,7 @@ public StoredConfig getConfig() { @Override public void scanForRepoChanges() throws IOException { - getRefDatabase().clearCache(); + getRefDatabase().refresh(); getObjectDatabase().clearCache(); } From 4c9eda17bea2a5f9c2d4905d2a9e43cc832a432d Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 6 Jan 2016 12:05:46 -0800 Subject: [PATCH 62/89] InMemoryRepository: Abort BatchRefUpdate if a command previously failed If any command has already been marked as failing, fail the entire batch. Change-Id: I1692240841aa4f4cb252bdccbc6f11d9246929c1 --- .../jgit/internal/storage/dfs/InMemoryRepository.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java index cdebb7a8f..205d3c7f8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java @@ -310,6 +310,11 @@ private void batch(RevWalk walk, List cmds) { Map peeled = new HashMap<>(); try (RevWalk rw = new RevWalk(getRepository())) { for (ReceiveCommand c : cmds) { + if (c.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) { + reject(cmds); + return; + } + if (!ObjectId.zeroId().equals(c.getNewId())) { try { RevObject o = rw.parseAny(c.getNewId()); From 24cd8e170d377cef87166d43ca25bfa2340755c6 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 6 Jan 2016 10:17:52 -0800 Subject: [PATCH 63/89] GitServletResponseTest: Fix testObjectCheckerException The recent ObjectChecker changes to pass in AnyObjectId as part of the checkCommit method signature meant the override here was no longer throwing an exception as expected. Change-Id: I0383018b48426e25a0bc562387e8cd73cbe13129 --- .../org/eclipse/jgit/http/test/GitServletResponseTests.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java index fba1a5264..4b15d4b53 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletResponseTests.java @@ -60,6 +60,7 @@ import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.http.HttpTestCase; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; @@ -221,8 +222,9 @@ public void testObjectCheckerException() throws Exception { preHook = null; oc = new ObjectChecker() { @Override - public void checkCommit(byte[] raw) throws CorruptObjectException { - throw new IllegalStateException(); + public void checkCommit(AnyObjectId id, byte[] raw) + throws CorruptObjectException { + throw new CorruptObjectException("refusing all commits"); } }; From 24468f09e3fb991ea5a6af293f84c7fe37e78b95 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Sun, 3 Jan 2016 15:27:01 +0100 Subject: [PATCH 64/89] Added CLIText.fatalError(String) API for tests In different places (Main, TextBuiltin, CLIGitCommand) we report fatal errors and at same time want to check for fatal errors in the tests. Using common API simplifies the error testing and helps to navigate to the actual error check implementation. Change-Id: Iecde79beb33ea595171f168f46b0b10ab2f339bb Signed-off-by: Andrey Loskutov --- .../src/org/eclipse/jgit/pgm/CLIGitCommand.java | 4 +--- .../tst/org/eclipse/jgit/pgm/DescribeTest.java | 12 ++++++------ .../tst/org/eclipse/jgit/pgm/MergeTest.java | 6 ++++-- .../src/org/eclipse/jgit/pgm/Main.java | 8 ++++---- .../src/org/eclipse/jgit/pgm/TextBuiltin.java | 2 +- .../src/org/eclipse/jgit/pgm/internal/CLIText.java | 13 +++++++++++++ 6 files changed, 29 insertions(+), 16 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java index 7d2cbca72..6a12019d7 100644 --- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java +++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java @@ -44,7 +44,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; @@ -106,8 +105,7 @@ public static List execute(String str, Repository db) try { return IO.readLines(new String(rawExecute(str, db))); } catch (Die e) { - return IO.readLines(MessageFormat.format(CLIText.get().fatalError, - e.getMessage())); + return IO.readLines(CLIText.fatalError(e.getMessage())); } } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java index a50b243ee..086e72e9a 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DescribeTest.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.pgm; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -50,6 +51,7 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.CLIRepositoryTestCase; +import org.eclipse.jgit.pgm.internal.CLIText; import org.junit.Before; import org.junit.Test; @@ -71,17 +73,15 @@ private void initialCommitAndTag() throws Exception { @Test public void testNoHead() throws Exception { - assertArrayEquals( - new String[] { "fatal: No names found, cannot describe anything." }, - executeUnchecked("git describe")); + assertEquals(CLIText.fatalError(CLIText.get().noNamesFound), + toString(executeUnchecked("git describe"))); } @Test public void testHeadNoTag() throws Exception { git.commit().setMessage("initial commit").call(); - assertArrayEquals( - new String[] { "fatal: No names found, cannot describe anything." }, - executeUnchecked("git describe")); + assertEquals(CLIText.fatalError(CLIText.get().noNamesFound), + toString(executeUnchecked("git describe"))); } @Test diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java index 641e59b04..47199016d 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/MergeTest.java @@ -50,6 +50,7 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.CLIRepositoryTestCase; import org.eclipse.jgit.merge.MergeStrategy; +import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Before; import org.junit.Test; @@ -194,7 +195,8 @@ public void testNoFastForward() throws Exception { @Test public void testNoFastForwardAndSquash() throws Exception { - assertEquals("fatal: You cannot combine --squash with --no-ff.", + assertEquals( + CLIText.fatalError(CLIText.get().cannotCombineSquashWithNoff), executeUnchecked("git merge master --no-ff --squash")[0]); } @@ -209,7 +211,7 @@ public void testFastForwardOnly() throws Exception { git.add().addFilepattern("file").call(); git.commit().setMessage("commit#2").call(); - assertEquals("fatal: Not possible to fast-forward, aborting.", + assertEquals(CLIText.fatalError(CLIText.get().ffNotPossibleAborting), executeUnchecked("git merge master --ff-only")[0]); } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java index d04cef0c7..e31306b21 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java @@ -128,7 +128,7 @@ protected void run(final String[] argv) { } catch (Die err) { if (err.isAborted()) System.exit(1); - System.err.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage())); + System.err.println(CLIText.fatalError(err.getMessage())); if (showStackTrace) err.printStackTrace(); System.exit(128); @@ -147,10 +147,10 @@ protected void run(final String[] argv) { } if (!showStackTrace && err.getCause() != null && err instanceof TransportException) - System.err.println(MessageFormat.format(CLIText.get().fatalError, err.getCause().getMessage())); + System.err.println(CLIText.fatalError(err.getCause().getMessage())); if (err.getClass().getName().startsWith("org.eclipse.jgit.errors.")) { //$NON-NLS-1$ - System.err.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage())); + System.err.println(CLIText.fatalError(err.getMessage())); if (showStackTrace) err.printStackTrace(); System.exit(128); @@ -176,7 +176,7 @@ private void execute(final String[] argv) throws Exception { clp.parseArgument(argv); } catch (CmdLineException err) { if (argv.length > 0 && !help && !version) { - writer.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage())); + writer.println(CLIText.fatalError(err.getMessage())); writer.flush(); System.exit(1); } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java index 95938326f..c4c5d64a0 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java @@ -216,7 +216,7 @@ protected void parseArguments(final String[] args) throws IOException { try { clp.parseArgument(args); } catch (CmdLineException err) { - this.errw.println(MessageFormat.format(CLIText.get().fatalError, err.getMessage())); + this.errw.println(CLIText.fatalError(err.getMessage())); if (help) { printUsage("", clp); //$NON-NLS-1$ } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java index fd86ddf2b..281213726 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java @@ -74,6 +74,19 @@ public static String formatLine(String line) { return MessageFormat.format(get().lineFormat, line); } + /** + * Format the given argument as fatal error using the format defined by + * {@link #fatalError} ("fatal: " by default). + * + * @param message + * the message to format + * @return the formatted line + * @since 4.2 + */ + public static String fatalError(String message) { + return MessageFormat.format(get().fatalError, message); + } + // @formatter:off /***/ public String alreadyOnBranch; /***/ public String alreadyUpToDate; From fb5056c2c5e96b815abe568af588157083c66197 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Sun, 3 Jan 2016 16:15:34 +0100 Subject: [PATCH 65/89] branch command: print help if requested, even if arguments are wrong git branch -d -h reports an error (because of missing -d option value) but does not print the help as expected. To fix this, CmdLineParser must catch, print but do not propagate exceptions if help is requested. Bug: 484951 Change-Id: I51265ebe295f22da540792c6a1980b8bdb295a02 Signed-off-by: Andrey Loskutov --- .../tst/org/eclipse/jgit/pgm/BranchTest.java | 10 +++++++ .../src/org/eclipse/jgit/pgm/TextBuiltin.java | 12 ++++++-- .../eclipse/jgit/pgm/opt/CmdLineParser.java | 29 +++++++++++++++++-- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java index 74506bc94..f1a53d7dc 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/BranchTest.java @@ -53,6 +53,7 @@ import org.eclipse.jgit.lib.CLIRepositoryTestCase; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Before; import org.junit.Test; @@ -65,6 +66,15 @@ public void setUp() throws Exception { new Git(db).commit().setMessage("initial commit").call(); } + @Test + public void testHelpAfterDelete() throws Exception { + String err = toString(executeUnchecked("git branch -d")); + String help = toString(executeUnchecked("git branch -h")); + String errAndHelp = toString(executeUnchecked("git branch -d -h")); + assertEquals(CLIText.fatalError(CLIText.get().branchNameRequired), err); + assertEquals(toString(err, help), errAndHelp); + } + @Test public void testList() throws Exception { assertEquals("* master", toString(execute("git branch"))); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java index c4c5d64a0..0dc549c7d 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java @@ -277,8 +277,16 @@ protected void printUsage(final String message, final CmdLineParser clp) } /** - * @return the resource bundle that will be passed to args4j for purpose - * of string localization + * @return error writer, typically this is standard error. + * @since 4.2 + */ + public ThrowingPrintWriter getErrorWriter() { + return errw; + } + + /** + * @return the resource bundle that will be passed to args4j for purpose of + * string localization */ protected ResourceBundle getResourceBundle() { return CLIText.get().resourceBundle(); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java index a1e39502c..b531ba65a 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.pgm.opt; +import java.io.IOException; import java.io.Writer; import java.lang.reflect.Field; import java.util.ArrayList; @@ -53,6 +54,7 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.pgm.Die; import org.eclipse.jgit.pgm.TextBuiltin; import org.eclipse.jgit.pgm.internal.CLIText; import org.eclipse.jgit.revwalk.RevCommit; @@ -95,6 +97,8 @@ public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser { private boolean seenHelp; + private TextBuiltin cmd; + /** * Creates a new command line owner that parses arguments/options and set * them into the given object. @@ -126,8 +130,12 @@ public CmdLineParser(final Object bean) { */ public CmdLineParser(final Object bean, Repository repo) { super(bean); - if (repo == null && bean instanceof TextBuiltin) - repo = ((TextBuiltin) bean).getRepository(); + if (bean instanceof TextBuiltin) { + cmd = (TextBuiltin) bean; + } + if (repo == null && cmd != null) { + repo = cmd.getRepository(); + } this.db = repo; } @@ -167,6 +175,11 @@ public void parseArgument(final String... args) throws CmdLineException { try { super.parseArgument(tmp.toArray(new String[tmp.size()])); + } catch (Die e) { + if (!seenHelp) { + throw e; + } + printToErrorWriter(CLIText.fatalError(e.getMessage())); } finally { // reset "required" options to defaults for correct command printout if (backup != null && !backup.isEmpty()) { @@ -176,6 +189,18 @@ public void parseArgument(final String... args) throws CmdLineException { } } + private void printToErrorWriter(String error) { + if (cmd == null) { + System.err.println(error); + } else { + try { + cmd.getErrorWriter().println(error); + } catch (IOException e1) { + System.err.println(error); + } + } + } + private List unsetRequiredOptions() { List options = getOptions(); List backup = new ArrayList<>(options); From 7780a4ee31df0e73f9519aaa51fd349a70b18e71 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Sun, 3 Jan 2016 12:29:55 +0100 Subject: [PATCH 66/89] Make sure CLIGitCommand and Main produce (almost) same results Currently execution of tests in pgm uses CLIGitCommand which re-implements few things from Main. Unfortunately this can results in a different test behavior compared to the real CLI runtime. The change let CLIGitCommand extend Main and only slightly modifies the runtime (stream redirection and undesired exit() termination). Change-Id: I87b7b61d1c84a89e5917610d84409f01be90b70b Signed-off-by: Andrey Loskutov --- .../jgit/lib/CLIRepositoryTestCase.java | 15 +- .../org/eclipse/jgit/pgm/CLIGitCommand.java | 161 +++++++++++------- .../tst/org/eclipse/jgit/pgm/ArchiveTest.java | 83 ++++----- .../src/org/eclipse/jgit/pgm/Main.java | 111 ++++++++---- 4 files changed, 230 insertions(+), 140 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java index a3436a017..a6af077aa 100644 --- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java +++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/lib/CLIRepositoryTestCase.java @@ -54,7 +54,8 @@ import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; import org.eclipse.jgit.pgm.CLIGitCommand; -import org.eclipse.jgit.pgm.Die; +import org.eclipse.jgit.pgm.CLIGitCommand.Result; +import org.eclipse.jgit.pgm.TextBuiltin.TerminatedByHelpException; import org.junit.Before; public class CLIRepositoryTestCase extends LocalDiskRepositoryTestCase { @@ -84,7 +85,7 @@ public void setUp() throws Exception { protected String[] executeUnchecked(String... cmds) throws Exception { List result = new ArrayList(cmds.length); for (String cmd : cmds) { - result.addAll(CLIGitCommand.execute(cmd, db)); + result.addAll(CLIGitCommand.executeUnchecked(cmd, db)); } return result.toArray(new String[0]); } @@ -102,11 +103,13 @@ protected String[] executeUnchecked(String... cmds) throws Exception { protected String[] execute(String... cmds) throws Exception { List result = new ArrayList(cmds.length); for (String cmd : cmds) { - List out = CLIGitCommand.execute(cmd, db); - if (contains(out, "fatal: ")) { - throw new Die(toString(out)); + Result r = CLIGitCommand.executeRaw(cmd, db); + if (r.ex instanceof TerminatedByHelpException) { + result.addAll(r.errLines()); + } else if (r.ex != null) { + throw r.ex; } - result.addAll(out); + result.addAll(r.outLines()); } return result.toArray(new String[0]); } diff --git a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java index 6a12019d7..3f396563c 100644 --- a/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java +++ b/org.eclipse.jgit.pgm.test/src/org/eclipse/jgit/pgm/CLIGitCommand.java @@ -42,33 +42,31 @@ */ package org.eclipse.jgit.pgm; +import static org.junit.Assert.assertNull; + import java.io.ByteArrayOutputStream; import java.io.File; + +import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.pgm.TextBuiltin.TerminatedByHelpException; -import org.eclipse.jgit.pgm.internal.CLIText; -import org.eclipse.jgit.pgm.opt.CmdLineParser; -import org.eclipse.jgit.pgm.opt.SubcommandHandler; import org.eclipse.jgit.util.IO; -import org.kohsuke.args4j.Argument; -public class CLIGitCommand { - @Argument(index = 0, metaVar = "metaVar_command", required = true, handler = SubcommandHandler.class) - private TextBuiltin subcommand; +public class CLIGitCommand extends Main { - @Argument(index = 1, metaVar = "metaVar_arg") - private List arguments = new ArrayList(); + private final Result result; - public TextBuiltin getSubcommand() { - return subcommand; - } + private final Repository db; - public List getArguments() { - return arguments; + public CLIGitCommand(Repository db) { + super(); + this.db = db; + result = new Result(); } /** @@ -102,57 +100,82 @@ public static void main(String[] args) throws Exception { public static List execute(String str, Repository db) throws Exception { + Result result = executeRaw(str, db); + return getOutput(result); + } + + public static Result executeRaw(String str, Repository db) + throws Exception { + CLIGitCommand cmd = new CLIGitCommand(db); + cmd.run(str); + return cmd.result; + } + + public static List executeUnchecked(String str, Repository db) + throws Exception { + CLIGitCommand cmd = new CLIGitCommand(db); try { - return IO.readLines(new String(rawExecute(str, db))); - } catch (Die e) { - return IO.readLines(CLIText.fatalError(e.getMessage())); + cmd.run(str); + return getOutput(cmd.result); + } catch (Throwable e) { + return cmd.result.errLines(); } } - public static byte[] rawExecute(String str, Repository db) + private static List getOutput(Result result) { + if (result.ex instanceof TerminatedByHelpException) { + return result.errLines(); + } + return result.outLines(); + } + + private void run(String commandLine) throws Exception { + String[] argv = convertToMainArgs(commandLine); + try { + super.run(argv); + } catch (TerminatedByHelpException e) { + // this is not a failure, super called exit() on help + } finally { + writer.flush(); + } + } + + private static String[] convertToMainArgs(String str) throws Exception { String[] args = split(str); - if (!args[0].equalsIgnoreCase("git") || args.length < 2) + if (!args[0].equalsIgnoreCase("git") || args.length < 2) { throw new IllegalArgumentException( "Expected 'git []', was:" + str); + } String[] argv = new String[args.length - 1]; System.arraycopy(args, 1, argv, 0, args.length - 1); + return argv; + } - CLIGitCommand bean = new CLIGitCommand(); - final CmdLineParser clp = new TestCmdLineParser(bean); - clp.parseArgument(argv); + @Override + PrintWriter createErrorWriter() { + return new PrintWriter(result.err); + } - final TextBuiltin cmd = bean.getSubcommand(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - cmd.outs = baos; - ByteArrayOutputStream errs = new ByteArrayOutputStream(); - cmd.errs = errs; - boolean seenHelp = TextBuiltin.containsHelp(argv); - if (cmd.requiresRepository()) - cmd.init(db, null); - else - cmd.init(null, null); - try { - cmd.execute(bean.getArguments().toArray( - new String[bean.getArguments().size()])); - } catch (TerminatedByHelpException e) { - seenHelp = true; - // this is not a failure, command execution should just not happen - } finally { - if (cmd.outw != null) { - cmd.outw.flush(); - } - if (cmd.errw != null) { - cmd.errw.flush(); - } - if (seenHelp) { - return errs.toByteArray(); - } else if (errs.size() > 0) { - // forward the errors to the standard err - System.err.print(errs.toString()); - } + void init(final TextBuiltin cmd) throws IOException { + cmd.outs = result.out; + cmd.errs = result.err; + super.init(cmd); + } + + @Override + protected Repository openGitDir(String aGitdir) throws IOException { + assertNull(aGitdir); + return db; + } + + @Override + void exit(int status, Exception t) throws Exception { + if (t == null) { + t = new IllegalStateException(Integer.toString(status)); } - return baos.toByteArray(); + result.ex = t; + throw t; } /** @@ -210,14 +233,36 @@ else if (r.length() > 0) { return list.toArray(new String[list.size()]); } - static class TestCmdLineParser extends CmdLineParser { - public TestCmdLineParser(Object bean) { - super(bean); + public static class Result { + public final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + public final ByteArrayOutputStream err = new ByteArrayOutputStream(); + + public Exception ex; + + public byte[] outBytes() { + return out.toByteArray(); } - @Override - protected boolean containsHelp(String... args) { - return false; + public byte[] errBytes() { + return err.toByteArray(); + } + + public String outString() { + return out.toString(); + } + + public List outLines() { + return IO.readLines(out.toString()); + } + + public String errString() { + return err.toString(); + } + + public List errLines() { + return IO.readLines(err.toString()); } } + } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java index 2e02c762b..a503ffdad 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ArchiveTest.java @@ -87,21 +87,22 @@ public void setUp() throws Exception { @Test public void testEmptyArchive() throws Exception { - byte[] result = CLIGitCommand.rawExecute( - "git archive --format=zip " + emptyTree, db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --format=zip " + emptyTree, db).outBytes(); assertArrayEquals(new String[0], listZipEntries(result)); } @Test public void testEmptyTar() throws Exception { - byte[] result = CLIGitCommand.rawExecute( - "git archive --format=tar " + emptyTree, db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --format=tar " + emptyTree, db).outBytes(); assertArrayEquals(new String[0], listTarEntries(result)); } @Test public void testUnrecognizedFormat() throws Exception { - String[] expect = new String[] { "fatal: Unknown archive format 'nonsense'" }; + String[] expect = new String[] { + "fatal: Unknown archive format 'nonsense'", "" }; String[] actual = executeUnchecked( "git archive --format=nonsense " + emptyTree); assertArrayEquals(expect, actual); @@ -116,8 +117,8 @@ public void testArchiveWithFiles() throws Exception { git.add().addFilepattern("c").call(); git.commit().setMessage("populate toplevel").call(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --format=zip HEAD", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --format=zip HEAD", db).outBytes(); assertArrayEquals(new String[] { "a", "c" }, listZipEntries(result)); } @@ -131,8 +132,8 @@ private void commitGreeting() throws Exception { @Test public void testDefaultFormatIsTar() throws Exception { commitGreeting(); - byte[] result = CLIGitCommand.rawExecute( - "git archive HEAD", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive HEAD", db).outBytes(); assertArrayEquals(new String[] { "greeting" }, listTarEntries(result)); } @@ -298,8 +299,8 @@ public void testArchiveWithSubdir() throws Exception { git.add().addFilepattern("b").call(); git.commit().setMessage("add subdir").call(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --format=zip master", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --format=zip master", db).outBytes(); String[] expect = { "a", "b.c", "b0c", "b/", "b/a", "b/b", "c" }; String[] actual = listZipEntries(result); @@ -324,8 +325,8 @@ public void testTarWithSubdir() throws Exception { git.add().addFilepattern("b").call(); git.commit().setMessage("add subdir").call(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --format=tar master", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --format=tar master", db).outBytes(); String[] expect = { "a", "b.c", "b0c", "b/", "b/a", "b/b", "c" }; String[] actual = listTarEntries(result); @@ -345,8 +346,8 @@ private void commitBazAndFooSlashBar() throws Exception { @Test public void testArchivePrefixOption() throws Exception { commitBazAndFooSlashBar(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --prefix=x/ --format=zip master", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --prefix=x/ --format=zip master", db).outBytes(); String[] expect = { "x/baz", "x/foo/", "x/foo/bar" }; String[] actual = listZipEntries(result); @@ -358,8 +359,8 @@ public void testArchivePrefixOption() throws Exception { @Test public void testTarPrefixOption() throws Exception { commitBazAndFooSlashBar(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --prefix=x/ --format=tar master", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --prefix=x/ --format=tar master", db).outBytes(); String[] expect = { "x/baz", "x/foo/", "x/foo/bar" }; String[] actual = listTarEntries(result); @@ -377,8 +378,8 @@ private void commitFoo() throws Exception { @Test public void testPrefixDoesNotNormalizeDoubleSlash() throws Exception { commitFoo(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --prefix=x// --format=zip master", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --prefix=x// --format=zip master", db).outBytes(); String[] expect = { "x//foo" }; assertArrayEquals(expect, listZipEntries(result)); } @@ -386,8 +387,8 @@ public void testPrefixDoesNotNormalizeDoubleSlash() throws Exception { @Test public void testPrefixDoesNotNormalizeDoubleSlashInTar() throws Exception { commitFoo(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --prefix=x// --format=tar master", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --prefix=x// --format=tar master", db).outBytes(); String[] expect = { "x//foo" }; assertArrayEquals(expect, listTarEntries(result)); } @@ -404,8 +405,8 @@ public void testPrefixDoesNotNormalizeDoubleSlashInTar() throws Exception { @Test public void testPrefixWithoutTrailingSlash() throws Exception { commitBazAndFooSlashBar(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --prefix=my- --format=zip master", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --prefix=my- --format=zip master", db).outBytes(); String[] expect = { "my-baz", "my-foo/", "my-foo/bar" }; String[] actual = listZipEntries(result); @@ -417,8 +418,8 @@ public void testPrefixWithoutTrailingSlash() throws Exception { @Test public void testTarPrefixWithoutTrailingSlash() throws Exception { commitBazAndFooSlashBar(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --prefix=my- --format=tar master", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --prefix=my- --format=tar master", db).outBytes(); String[] expect = { "my-baz", "my-foo/", "my-foo/bar" }; String[] actual = listTarEntries(result); @@ -437,8 +438,8 @@ public void testArchiveIncludesSubmoduleDirectory() throws Exception { git.submoduleAdd().setURI("./.").setPath("b").call().close(); git.commit().setMessage("add submodule").call(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --format=zip master", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --format=zip master", db).outBytes(); String[] expect = { ".gitmodules", "a", "b/", "c" }; String[] actual = listZipEntries(result); @@ -457,8 +458,8 @@ public void testTarIncludesSubmoduleDirectory() throws Exception { git.submoduleAdd().setURI("./.").setPath("b").call().close(); git.commit().setMessage("add submodule").call(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --format=tar master", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --format=tar master", db).outBytes(); String[] expect = { ".gitmodules", "a", "b/", "c" }; String[] actual = listTarEntries(result); @@ -487,8 +488,8 @@ public void testArchivePreservesMode() throws Exception { git.commit().setMessage("three files with different modes").call(); - byte[] zipData = CLIGitCommand.rawExecute( - "git archive --format=zip master", db); + byte[] zipData = CLIGitCommand.executeRaw( + "git archive --format=zip master", db).outBytes(); writeRaw("zip-with-modes.zip", zipData); assertContainsEntryWithMode("zip-with-modes.zip", "-rw-", "plain"); assertContainsEntryWithMode("zip-with-modes.zip", "-rwx", "executable"); @@ -516,8 +517,8 @@ public void testTarPreservesMode() throws Exception { git.commit().setMessage("three files with different modes").call(); - byte[] archive = CLIGitCommand.rawExecute( - "git archive --format=tar master", db); + byte[] archive = CLIGitCommand.executeRaw( + "git archive --format=tar master", db).outBytes(); writeRaw("with-modes.tar", archive); assertTarContainsEntry("with-modes.tar", "-rw-r--r--", "plain"); assertTarContainsEntry("with-modes.tar", "-rwxr-xr-x", "executable"); @@ -539,8 +540,8 @@ public void testArchiveWithLongFilename() throws Exception { git.add().addFilepattern("1234567890").call(); git.commit().setMessage("file with long name").call(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --format=zip HEAD", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --format=zip HEAD", db).outBytes(); assertArrayEquals(l.toArray(new String[l.size()]), listZipEntries(result)); } @@ -559,8 +560,8 @@ public void testTarWithLongFilename() throws Exception { git.add().addFilepattern("1234567890").call(); git.commit().setMessage("file with long name").call(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --format=tar HEAD", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --format=tar HEAD", db).outBytes(); assertArrayEquals(l.toArray(new String[l.size()]), listTarEntries(result)); } @@ -572,8 +573,8 @@ public void testArchivePreservesContent() throws Exception { git.add().addFilepattern("xyzzy").call(); git.commit().setMessage("add file with content").call(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --format=zip HEAD", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --format=zip HEAD", db).outBytes(); assertArrayEquals(new String[] { payload }, zipEntryContent(result, "xyzzy")); } @@ -585,8 +586,8 @@ public void testTarPreservesContent() throws Exception { git.add().addFilepattern("xyzzy").call(); git.commit().setMessage("add file with content").call(); - byte[] result = CLIGitCommand.rawExecute( - "git archive --format=tar HEAD", db); + byte[] result = CLIGitCommand.executeRaw( + "git archive --format=tar HEAD", db).outBytes(); assertArrayEquals(new String[] { payload }, tarEntryContent(result, "xyzzy")); } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java index e31306b21..d701f22c3 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java @@ -90,14 +90,23 @@ public class Main { @Argument(index = 1, metaVar = "metaVar_arg") private List arguments = new ArrayList(); + PrintWriter writer; + + /** + * + */ + public Main() { + HttpTransport.setConnectionFactory(new HttpClientConnectionFactory()); + } + /** * Execute the command line. * * @param argv * arguments. + * @throws Exception */ - public static void main(final String[] argv) { - HttpTransport.setConnectionFactory(new HttpClientConnectionFactory()); + public static void main(final String[] argv) throws Exception { new Main().run(argv); } @@ -116,8 +125,10 @@ public static void main(final String[] argv) { * * @param argv * arguments. + * @throws Exception */ - protected void run(final String[] argv) { + protected void run(final String[] argv) throws Exception { + writer = createErrorWriter(); try { if (!installConsole()) { AwtAuthenticator.install(); @@ -126,12 +137,14 @@ protected void run(final String[] argv) { configureHttpProxy(); execute(argv); } catch (Die err) { - if (err.isAborted()) - System.exit(1); - System.err.println(CLIText.fatalError(err.getMessage())); - if (showStackTrace) - err.printStackTrace(); - System.exit(128); + if (err.isAborted()) { + exit(1, err); + } + writer.println(CLIText.fatalError(err.getMessage())); + if (showStackTrace) { + err.printStackTrace(writer); + } + exit(128, err); } catch (Exception err) { // Try to detect errno == EPIPE and exit normally if that happens // There may be issues with operating system versions and locale, @@ -139,46 +152,54 @@ protected void run(final String[] argv) { // under other circumstances. if (err.getClass() == IOException.class) { // Linux, OS X - if (err.getMessage().equals("Broken pipe")) //$NON-NLS-1$ - System.exit(0); + if (err.getMessage().equals("Broken pipe")) { //$NON-NLS-1$ + exit(0, err); + } // Windows - if (err.getMessage().equals("The pipe is being closed")) //$NON-NLS-1$ - System.exit(0); + if (err.getMessage().equals("The pipe is being closed")) { //$NON-NLS-1$ + exit(0, err); + } } if (!showStackTrace && err.getCause() != null - && err instanceof TransportException) - System.err.println(CLIText.fatalError(err.getCause().getMessage())); + && err instanceof TransportException) { + writer.println(CLIText.fatalError(err.getCause().getMessage())); + } if (err.getClass().getName().startsWith("org.eclipse.jgit.errors.")) { //$NON-NLS-1$ - System.err.println(CLIText.fatalError(err.getMessage())); - if (showStackTrace) + writer.println(CLIText.fatalError(err.getMessage())); + if (showStackTrace) { err.printStackTrace(); - System.exit(128); + } + exit(128, err); } err.printStackTrace(); - System.exit(1); + exit(1, err); } if (System.out.checkError()) { - System.err.println(CLIText.get().unknownIoErrorStdout); - System.exit(1); + writer.println(CLIText.get().unknownIoErrorStdout); + exit(1, null); } - if (System.err.checkError()) { + if (writer.checkError()) { // No idea how to present an error here, most likely disk full or // broken pipe - System.exit(1); + exit(1, null); } } + PrintWriter createErrorWriter() { + return new PrintWriter(System.err); + } + private void execute(final String[] argv) throws Exception { final CmdLineParser clp = new SubcommandLineParser(this); - PrintWriter writer = new PrintWriter(System.err); + try { clp.parseArgument(argv); } catch (CmdLineException err) { if (argv.length > 0 && !help && !version) { writer.println(CLIText.fatalError(err.getMessage())); writer.flush(); - System.exit(1); + exit(1, err); } } @@ -194,22 +215,24 @@ private void execute(final String[] argv) throws Exception { writer.println(CLIText.get().mostCommonlyUsedCommandsAre); final CommandRef[] common = CommandCatalog.common(); int width = 0; - for (final CommandRef c : common) + for (final CommandRef c : common) { width = Math.max(width, c.getName().length()); + } width += 2; for (final CommandRef c : common) { writer.print(' '); writer.print(c.getName()); - for (int i = c.getName().length(); i < width; i++) + for (int i = c.getName().length(); i < width; i++) { writer.print(' '); + } writer.print(CLIText.get().resourceBundle().getString(c.getUsage())); writer.println(); } writer.println(); } writer.flush(); - System.exit(1); + exit(1, null); } if (version) { @@ -218,20 +241,38 @@ private void execute(final String[] argv) throws Exception { } final TextBuiltin cmd = subcommand; - if (cmd.requiresRepository()) - cmd.init(openGitDir(gitdir), null); - else - cmd.init(null, gitdir); + init(cmd); try { cmd.execute(arguments.toArray(new String[arguments.size()])); } finally { - if (cmd.outw != null) + if (cmd.outw != null) { cmd.outw.flush(); - if (cmd.errw != null) + } + if (cmd.errw != null) { cmd.errw.flush(); + } } } + void init(final TextBuiltin cmd) throws IOException { + if (cmd.requiresRepository()) { + cmd.init(openGitDir(gitdir), null); + } else { + cmd.init(null, gitdir); + } + } + + /** + * @param status + * @param t + * can be {@code null} + * @throws Exception + */ + void exit(int status, Exception t) throws Exception { + writer.flush(); + System.exit(status); + } + /** * Evaluate the {@code --git-dir} option and open the repository. * @@ -281,7 +322,7 @@ private static void install(final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { try { - Class.forName(name).getMethod("install").invoke(null); //$NON-NLS-1$ + Class.forName(name).getMethod("install").invoke(null); //$NON-NLS-1$ } catch (InvocationTargetException e) { if (e.getCause() instanceof RuntimeException) throw (RuntimeException) e.getCause(); From 73eb32be829c0f2167c2796ce1ba60bdf4a6b88a Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Tue, 17 Nov 2015 16:22:18 -0800 Subject: [PATCH 67/89] RefTree: Store references in a Git tree A group of updates can be applied by updating the tree in one step, writing out a new root tree, and storing its SHA-1. If references are stored in RefTrees, comparing two repositories is a matter of checking if two SHA-1s are identical. Without RefTrees comparing two repositories requires listing all references and comparing the sets. Track the "refs/" directory as a root tree by storing references that point directly at an object as a GITLINK entry in the tree. For example "refs/heads/master" is written as "heads/master". Annotated tags also store their peeled value with ^{} suffix, using "tags/v1.0" and "tags/v1.0^{}" GITLINK entries. Symbolic references are written as SYMLINK entries with the blob of the symlink carrying the name of the symbolic reference target. HEAD is outside of "refs/" namespace so it is stored as a special "..HEAD" entry. This name is chosen because ".." is not valid in a reference name and it almost looks like "../HEAD" which names HEAD if the reader was inside of the "refs/" directory. A new Command type is required to handle symbolic references and peeled references. Change-Id: Id47e5d4d32149a9e500854147edd7d93c1041a39 --- org.eclipse.jgit.test/META-INF/MANIFEST.MF | 1 + .../internal/storage/reftree/RefTreeTest.java | 303 +++++++++++++ org.eclipse.jgit/META-INF/MANIFEST.MF | 1 + .../eclipse/jgit/dircache/DirCacheEntry.java | 17 + .../internal/storage/reftree/Command.java | 290 +++++++++++++ .../internal/storage/reftree/RefTree.java | 410 ++++++++++++++++++ .../src/org/eclipse/jgit/lib/RefDatabase.java | 4 +- 7 files changed, 1025 insertions(+), 1 deletion(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 37fd36717..f78fe5b24 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -26,6 +26,7 @@ Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)", org.eclipse.jgit.internal.storage.dfs;version="[4.2.0,4.3.0)", org.eclipse.jgit.internal.storage.file;version="[4.2.0,4.3.0)", org.eclipse.jgit.internal.storage.pack;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.reftree;version="[4.2.0,4.3.0)", org.eclipse.jgit.junit;version="[4.2.0,4.3.0)", org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", org.eclipse.jgit.merge;version="[4.2.0,4.3.0)", diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeTest.java new file mode 100644 index 000000000..8e0f38c69 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeTest.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.Constants.R_TAGS; +import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.junit.Before; +import org.junit.Test; + +public class RefTreeTest { + private static final String R_MASTER = R_HEADS + "master"; + private InMemoryRepository repo; + private TestRepository git; + + @Before + public void setUp() throws IOException { + repo = new InMemoryRepository(new DfsRepositoryDescription("RefTree")); + git = new TestRepository<>(repo); + } + + @Test + public void testEmptyTree() throws IOException { + RefTree tree = RefTree.newEmptyTree(); + try (ObjectReader reader = repo.newObjectReader()) { + assertNull(HEAD, tree.exactRef(reader, HEAD)); + assertNull("master", tree.exactRef(reader, R_MASTER)); + } + } + + @Test + public void testApplyThenReadMaster() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob id = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, id)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + assertSame(NOT_ATTEMPTED, cmd.getResult()); + + try (ObjectReader reader = repo.newObjectReader()) { + Ref m = tree.exactRef(reader, R_MASTER); + assertNotNull(R_MASTER, m); + assertEquals(R_MASTER, m.getName()); + assertEquals(id, m.getObjectId()); + assertTrue("peeled", m.isPeeled()); + } + } + + @Test + public void testUpdateMaster() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob id1 = git.blob("A"); + Command cmd1 = new Command(null, ref(R_MASTER, id1)); + assertTrue(tree.apply(Collections.singletonList(cmd1))); + assertSame(NOT_ATTEMPTED, cmd1.getResult()); + + RevBlob id2 = git.blob("B"); + Command cmd2 = new Command(ref(R_MASTER, id1), ref(R_MASTER, id2)); + assertTrue(tree.apply(Collections.singletonList(cmd2))); + assertSame(NOT_ATTEMPTED, cmd2.getResult()); + + try (ObjectReader reader = repo.newObjectReader()) { + Ref m = tree.exactRef(reader, R_MASTER); + assertNotNull(R_MASTER, m); + assertEquals(R_MASTER, m.getName()); + assertEquals(id2, m.getObjectId()); + assertTrue("peeled", m.isPeeled()); + } + } + + @Test + public void testHeadSymref() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob id = git.blob("A"); + Command cmd1 = new Command(null, ref(R_MASTER, id)); + Command cmd2 = new Command(null, symref(HEAD, R_MASTER)); + assertTrue(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 }))); + assertSame(NOT_ATTEMPTED, cmd1.getResult()); + assertSame(NOT_ATTEMPTED, cmd2.getResult()); + + try (ObjectReader reader = repo.newObjectReader()) { + Ref m = tree.exactRef(reader, HEAD); + assertNotNull(HEAD, m); + assertEquals(HEAD, m.getName()); + assertTrue("symbolic", m.isSymbolic()); + assertNotNull(m.getTarget()); + assertEquals(R_MASTER, m.getTarget().getName()); + assertEquals(id, m.getTarget().getObjectId()); + } + + // Writing flushes some buffers, re-read from blob. + ObjectId newId = write(tree); + try (ObjectReader reader = repo.newObjectReader(); + RevWalk rw = new RevWalk(reader)) { + tree = RefTree.read(reader, rw.parseTree(newId)); + Ref m = tree.exactRef(reader, HEAD); + assertEquals(R_MASTER, m.getTarget().getName()); + } + } + + @Test + public void testTagIsPeeled() throws Exception { + String name = "v1.0"; + RefTree tree = RefTree.newEmptyTree(); + RevBlob id = git.blob("A"); + RevTag tag = git.tag(name, id); + + String ref = R_TAGS + name; + Command cmd = create(ref, tag); + assertTrue(tree.apply(Collections.singletonList(cmd))); + assertSame(NOT_ATTEMPTED, cmd.getResult()); + + try (ObjectReader reader = repo.newObjectReader()) { + Ref m = tree.exactRef(reader, ref); + assertNotNull(ref, m); + assertEquals(ref, m.getName()); + assertEquals(tag, m.getObjectId()); + assertTrue("peeled", m.isPeeled()); + assertEquals(id, m.getPeeledObjectId()); + } + } + + @Test + public void testApplyAlreadyExists() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob a = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, a)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + ObjectId treeId = write(tree); + + RevBlob b = git.blob("B"); + Command cmd1 = create(R_MASTER, b); + Command cmd2 = create(R_MASTER, b); + assertFalse(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 }))); + assertSame(LOCK_FAILURE, cmd1.getResult()); + assertSame(REJECTED_OTHER_REASON, cmd2.getResult()); + assertEquals(JGitText.get().transactionAborted, cmd2.getMessage()); + assertEquals(treeId, write(tree)); + } + + @Test + public void testApplyWrongOldId() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob a = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, a)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + ObjectId treeId = write(tree); + + RevBlob b = git.blob("B"); + RevBlob c = git.blob("C"); + Command cmd1 = update(R_MASTER, b, c); + Command cmd2 = create(R_MASTER, b); + assertFalse(tree.apply(Arrays.asList(new Command[] { cmd1, cmd2 }))); + assertSame(LOCK_FAILURE, cmd1.getResult()); + assertSame(REJECTED_OTHER_REASON, cmd2.getResult()); + assertEquals(JGitText.get().transactionAborted, cmd2.getMessage()); + assertEquals(treeId, write(tree)); + } + + @Test + public void testApplyWrongOldIdButAlreadyCurrentIsNoOp() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob a = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, a)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + ObjectId treeId = write(tree); + + RevBlob b = git.blob("B"); + cmd = update(R_MASTER, b, a); + assertTrue(tree.apply(Collections.singletonList(cmd))); + assertEquals(treeId, write(tree)); + } + + @Test + public void testApplyCannotCreateSubdirectory() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob a = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, a)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + ObjectId treeId = write(tree); + + RevBlob b = git.blob("B"); + Command cmd1 = create(R_MASTER + "/fail", b); + assertFalse(tree.apply(Collections.singletonList(cmd1))); + assertSame(LOCK_FAILURE, cmd1.getResult()); + assertEquals(treeId, write(tree)); + } + + @Test + public void testApplyCannotCreateParentRef() throws Exception { + RefTree tree = RefTree.newEmptyTree(); + RevBlob a = git.blob("A"); + Command cmd = new Command(null, ref(R_MASTER, a)); + assertTrue(tree.apply(Collections.singletonList(cmd))); + ObjectId treeId = write(tree); + + RevBlob b = git.blob("B"); + Command cmd1 = create("refs/heads", b); + assertFalse(tree.apply(Collections.singletonList(cmd1))); + assertSame(LOCK_FAILURE, cmd1.getResult()); + assertEquals(treeId, write(tree)); + } + + private static Ref ref(String name, ObjectId id) { + return new ObjectIdRef.PeeledNonTag(LOOSE, name, id); + } + + private static Ref symref(String name, String dest) { + Ref d = new ObjectIdRef.PeeledNonTag(NEW, dest, null); + return new SymbolicRef(name, d); + } + + private Command create(String name, ObjectId id) + throws MissingObjectException, IOException { + return update(name, ObjectId.zeroId(), id); + } + + private Command update(String name, ObjectId oldId, ObjectId newId) + throws MissingObjectException, IOException { + try (RevWalk rw = new RevWalk(repo)) { + return new Command(rw, new ReceiveCommand(oldId, newId, name)); + } + } + + private ObjectId write(RefTree tree) throws IOException { + try (ObjectInserter ins = repo.newObjectInserter()) { + ObjectId id = tree.writeTree(ins); + ins.flush(); + return id; + } + } +} diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 66529d3af..25d0be6ec 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -68,6 +68,7 @@ Export-Package: org.eclipse.jgit.annotations;version="4.2.0", org.eclipse.jgit.pgm.test, org.eclipse.jgit.pgm", org.eclipse.jgit.internal.storage.pack;version="4.2.0";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", + org.eclipse.jgit.internal.storage.reftree;version="4.2.0";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm", org.eclipse.jgit.lib;version="4.2.0"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java index 5df9e0b4d..4ebf2e0d7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java @@ -294,6 +294,23 @@ public DirCacheEntry(byte[] path, final int stage) { NB.encodeInt16(info, infoOffset + P_FLAGS, flags); } + /** + * Duplicate DirCacheEntry with same path and copied info. + *

+ * The same path buffer is reused (avoiding copying), however a new info + * buffer is created and its contents are copied. + * + * @param src + * entry to clone. + * @since 4.2 + */ + public DirCacheEntry(DirCacheEntry src) { + path = src.path; + info = new byte[INFO_LEN]; + infoOffset = 0; + System.arraycopy(src.info, src.infoOffset, info, 0, INFO_LEN); + } + void write(final OutputStream os) throws IOException { final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN; final int pathLen = path.length; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java new file mode 100644 index 000000000..9ac375f16 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.encode; +import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK; +import static org.eclipse.jgit.lib.Ref.Storage.NETWORK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; + +import java.io.IOException; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.transport.ReceiveCommand.Result; + +/** + * Command to create, update or delete an entry inside a {@link RefTree}. + *

+ * Unlike {@link ReceiveCommand} (which can only update a reference to an + * {@link ObjectId}), a RefTree Command can also create, modify or delete + * symbolic references to a target reference. + *

+ * RefTree Commands may wrap a {@code ReceiveCommand} to allow callers to + * process an existing ReceiveCommand against a RefTree. + *

+ * Commands should be passed into {@link RefTree#apply(java.util.Collection)} + * for processing. + */ +public class Command { + private final Ref oldRef; + private final Ref newRef; + private final ReceiveCommand cmd; + private Result result; + + /** + * Create a command to create, update or delete a reference. + *

+ * At least one of {@code oldRef} or {@code newRef} must be supplied. + * + * @param oldRef + * expected value. Null if the ref should not exist. + * @param newRef + * desired value, must be peeled if not null and not symbolic. + * Null to delete the ref. + */ + public Command(@Nullable Ref oldRef, @Nullable Ref newRef) { + this.oldRef = oldRef; + this.newRef = newRef; + this.cmd = null; + this.result = NOT_ATTEMPTED; + + if (oldRef == null && newRef == null) { + throw new IllegalArgumentException(); + } + if (newRef != null && !newRef.isPeeled() && !newRef.isSymbolic()) { + throw new IllegalArgumentException(); + } + if (oldRef != null && newRef != null + && !oldRef.getName().equals(newRef.getName())) { + throw new IllegalArgumentException(); + } + } + + /** + * Construct a RefTree command wrapped around a ReceiveCommand. + * + * @param rw + * walk instance to peel the {@code newId}. + * @param cmd + * command received from a push client. + * @throws MissingObjectException + * {@code oldId} or {@code newId} is missing. + * @throws IOException + * {@code oldId} or {@code newId} cannot be peeled. + */ + public Command(RevWalk rw, ReceiveCommand cmd) + throws MissingObjectException, IOException { + this.oldRef = toRef(rw, cmd.getOldId(), cmd.getRefName(), false); + this.newRef = toRef(rw, cmd.getNewId(), cmd.getRefName(), true); + this.cmd = cmd; + } + + private static Ref toRef(RevWalk rw, ObjectId id, String name, + boolean mustExist) throws MissingObjectException, IOException { + if (ObjectId.zeroId().equals(id)) { + return null; + } + + try { + RevObject o = rw.parseAny(id); + if (o instanceof RevTag) { + RevObject p = rw.peel(o); + return new ObjectIdRef.PeeledTag(NETWORK, name, id, p.copy()); + } + return new ObjectIdRef.PeeledNonTag(NETWORK, name, id); + } catch (MissingObjectException e) { + if (mustExist) { + throw e; + } + return new ObjectIdRef.Unpeeled(NETWORK, name, id); + } + } + + /** @return name of the reference affected by this command. */ + public String getRefName() { + if (cmd != null) { + return cmd.getRefName(); + } else if (newRef != null) { + return newRef.getName(); + } + return oldRef.getName(); + } + + /** + * Set the result of this command. + * + * @param result + * the command result. + */ + public void setResult(Result result) { + setResult(result, null); + } + + /** + * Set the result of this command. + * + * @param result + * the command result. + * @param why + * optional message explaining the result status. + */ + public void setResult(Result result, @Nullable String why) { + if (cmd != null) { + cmd.setResult(result, why); + } else { + this.result = result; + } + } + + /** @return result of executing this command. */ + public Result getResult() { + return cmd != null ? cmd.getResult() : result; + } + + /** @return optional message explaining command failure. */ + @Nullable + public String getMessage() { + return cmd != null ? cmd.getMessage() : null; + } + + /** + * Old peeled reference. + * + * @return the old reference; null if the command is creating the reference. + */ + @Nullable + public Ref getOldRef() { + return oldRef; + } + + /** + * New peeled reference. + * + * @return the new reference; null if the command is deleting the reference. + */ + @Nullable + public Ref getNewRef() { + return newRef; + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + append(s, oldRef, "CREATE"); //$NON-NLS-1$ + s.append(' '); + append(s, newRef, "DELETE"); //$NON-NLS-1$ + s.append(' ').append(getRefName()); + s.append(' ').append(getResult()); + if (getMessage() != null) { + s.append(' ').append(getMessage()); + } + return s.toString(); + } + + private static void append(StringBuilder s, Ref r, String nullName) { + if (r == null) { + s.append(nullName); + } else if (r.isSymbolic()) { + s.append(r.getTarget().getName()); + } else { + ObjectId id = r.getObjectId(); + if (id != null) { + s.append(id.name()); + } + } + } + + /** + * Check the entry is consistent with either the old or the new ref. + * + * @param entry + * current entry; null if the entry does not exist. + * @return true if entry matches {@link #getOldRef()} or + * {@link #getNewRef()}; otherwise false. + */ + boolean checkRef(@Nullable DirCacheEntry entry) { + if (entry != null && entry.getRawMode() == 0) { + entry = null; + } + return check(entry, oldRef) || check(entry, newRef); + } + + private static boolean check(@Nullable DirCacheEntry cur, + @Nullable Ref exp) { + if (cur == null) { + // Does not exist, ok if oldRef does not exist. + return exp == null; + } else if (exp == null) { + // Expected to not exist, but currently exists, fail. + return false; + } + + if (exp.isSymbolic()) { + String dst = exp.getTarget().getName(); + return cur.getRawMode() == TYPE_SYMLINK + && cur.getObjectId().equals(symref(dst)); + } + + return cur.getRawMode() == TYPE_GITLINK + && cur.getObjectId().equals(exp.getObjectId()); + } + + static ObjectId symref(String s) { + @SuppressWarnings("resource") + ObjectInserter.Formatter fmt = new ObjectInserter.Formatter(); + return fmt.idFor(OBJ_BLOB, encode(s)); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java new file mode 100644 index 000000000..237f7e995 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.R_REFS; +import static org.eclipse.jgit.lib.Constants.encode; +import static org.eclipse.jgit.lib.FileMode.GITLINK; +import static org.eclipse.jgit.lib.FileMode.SYMLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; +import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEditor; +import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath; +import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.DirCacheNameConflictException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * Tree of references in the reference graph. + *

+ * The root corresponds to the {@code "refs/"} subdirectory, for example the + * default reference {@code "refs/heads/master"} is stored at path + * {@code "heads/master"} in a {@code RefTree}. + *

+ * Normal references are stored as {@link FileMode#GITLINK} tree entries. The + * ObjectId in the tree entry is the ObjectId the reference refers to. + *

+ * Symbolic references are stored as {@link FileMode#SYMLINK} entries, with the + * blob storing the name of the target reference. + *

+ * Annotated tags also store the peeled object using a {@code GITLINK} entry + * with the suffix "^{}", for example {@code "tags/v1.0"} stores + * the annotated tag object, while "tags/v1.0^{}" stores the commit + * the tag annotates. + *

+ * {@code HEAD} is a special case and stored as {@code "..HEAD"}. + */ +public class RefTree { + /** Suffix applied to GITLINK to indicate its the peeled value of a tag. */ + public static final String PEELED_SUFFIX = "^{}"; //$NON-NLS-1$ + static final String ROOT_DOTDOT = ".."; //$NON-NLS-1$ + + /** + * Create an empty reference tree. + * + * @return a new empty reference tree. + */ + public static RefTree newEmptyTree() { + return new RefTree(DirCache.newInCore()); + } + + /** + * Load a reference tree. + * + * @param reader + * reader to scan the reference tree with. + * @param tree + * the tree to read. + * @return the ref tree read from the commit. + * @throws IOException + * the repository cannot be accessed through the reader. + * @throws CorruptObjectException + * a tree object is corrupt and cannot be read. + * @throws IncorrectObjectTypeException + * a tree object wasn't actually a tree. + * @throws MissingObjectException + * a reference tree object doesn't exist. + */ + public static RefTree read(ObjectReader reader, RevTree tree) + throws MissingObjectException, IncorrectObjectTypeException, + CorruptObjectException, IOException { + return new RefTree(DirCache.read(reader, tree)); + } + + private DirCache contents; + private Map pendingBlobs; + + private RefTree(DirCache dc) { + this.contents = dc; + } + + /** + * Read one reference. + *

+ * References are always returned peeled ({@link Ref#isPeeled()} is true). + * If the reference points to an annotated tag, the returned reference will + * be peeled and contain {@link Ref#getPeeledObjectId()}. + *

+ * If the reference is a symbolic reference and the chain depth is less than + * {@link org.eclipse.jgit.lib.RefDatabase#MAX_SYMBOLIC_REF_DEPTH} the + * returned reference is resolved. If the chain depth is longer, the + * symbolic reference is returned without resolving. + * + * @param reader + * to access objects necessary to read the requested reference. + * @param name + * name of the reference to read. + * @return the reference; null if it does not exist. + * @throws IOException + * cannot read a symbolic reference target. + */ + @Nullable + public Ref exactRef(ObjectReader reader, String name) throws IOException { + Ref r = readRef(reader, name); + if (r == null) { + return null; + } else if (r.isSymbolic()) { + return resolve(reader, r, 0); + } + + DirCacheEntry p = contents.getEntry(peeledPath(name)); + if (p != null && p.getRawMode() == TYPE_GITLINK) { + return new ObjectIdRef.PeeledTag(PACKED, r.getName(), + r.getObjectId(), p.getObjectId()); + } + return r; + } + + private Ref readRef(ObjectReader reader, String name) throws IOException { + DirCacheEntry e = contents.getEntry(refPath(name)); + return e != null ? toRef(reader, e, name) : null; + } + + private Ref toRef(ObjectReader reader, DirCacheEntry e, String name) + throws IOException { + int mode = e.getRawMode(); + if (mode == TYPE_GITLINK) { + ObjectId id = e.getObjectId(); + return new ObjectIdRef.PeeledNonTag(PACKED, name, id); + } + + if (mode == TYPE_SYMLINK) { + ObjectId id = e.getObjectId(); + String n = pendingBlobs != null ? pendingBlobs.get(id) : null; + if (n == null) { + byte[] bin = reader.open(id, OBJ_BLOB).getCachedBytes(); + n = RawParseUtils.decode(bin); + } + Ref dst = new ObjectIdRef.Unpeeled(NEW, n, null); + return new SymbolicRef(name, dst); + } + + return null; // garbage file or something; not a reference. + } + + private Ref resolve(ObjectReader reader, Ref ref, int depth) + throws IOException { + if (ref.isSymbolic() && depth < MAX_SYMBOLIC_REF_DEPTH) { + Ref r = readRef(reader, ref.getTarget().getName()); + if (r == null) { + return ref; + } + Ref dst = resolve(reader, r, depth + 1); + return new SymbolicRef(ref.getName(), dst); + } + return ref; + } + + /** + * Attempt a batch of commands against this RefTree. + *

+ * The batch is applied atomically, either all commands apply at once, or + * they all reject and the RefTree is left unmodified. + *

+ * On success (when this method returns {@code true}) the command results + * are left as-is (probably {@code NOT_ATTEMPTED}). Result fields are set + * only when this method returns {@code false} to indicate failure. + * + * @param cmdList + * to apply. All commands should still have result NOT_ATTEMPTED. + * @return true if the commands applied; false if they were rejected. + */ + public boolean apply(Collection cmdList) { + try { + DirCacheEditor ed = contents.editor(); + for (Command cmd : cmdList) { + apply(ed, cmd); + } + ed.finish(); + return true; + } catch (DirCacheNameConflictException e) { + String r1 = refName(e.getPath1()); + String r2 = refName(e.getPath2()); + for (Command cmd : cmdList) { + if (r1.equals(cmd.getRefName()) + || r2.equals(cmd.getRefName())) { + cmd.setResult(LOCK_FAILURE); + break; + } + } + return abort(cmdList); + } catch (LockFailureException e) { + return abort(cmdList); + } + } + + private void apply(DirCacheEditor ed, final Command cmd) { + String path = refPath(cmd.getRefName()); + Ref oldRef = cmd.getOldRef(); + final Ref newRef = cmd.getNewRef(); + + if (newRef == null) { + checkRef(contents.getEntry(path), cmd); + ed.add(new DeletePath(path)); + cleanupPeeledRef(ed, oldRef); + return; + } + + if (newRef.isSymbolic()) { + final String dst = newRef.getTarget().getName(); + ed.add(new PathEdit(path) { + @Override + public void apply(DirCacheEntry ent) { + checkRef(ent, cmd); + ObjectId id = Command.symref(dst); + ent.setFileMode(SYMLINK); + ent.setObjectId(id); + if (pendingBlobs == null) { + pendingBlobs = new HashMap<>(4); + } + pendingBlobs.put(id, dst); + } + }.setReplace(false)); + cleanupPeeledRef(ed, oldRef); + return; + } + + ed.add(new PathEdit(path) { + @Override + public void apply(DirCacheEntry ent) { + checkRef(ent, cmd); + ent.setFileMode(GITLINK); + ent.setObjectId(newRef.getObjectId()); + } + }.setReplace(false)); + + if (newRef.getPeeledObjectId() != null) { + ed.add(new PathEdit(peeledPath(newRef.getName())) { + @Override + public void apply(DirCacheEntry ent) { + ent.setFileMode(GITLINK); + ent.setObjectId(newRef.getPeeledObjectId()); + } + }.setReplace(false)); + } else { + cleanupPeeledRef(ed, oldRef); + } + } + + private static void checkRef(@Nullable DirCacheEntry ent, Command cmd) { + if (!cmd.checkRef(ent)) { + cmd.setResult(LOCK_FAILURE); + throw new LockFailureException(); + } + } + + private static void cleanupPeeledRef(DirCacheEditor ed, Ref ref) { + if (ref != null && !ref.isSymbolic() + && (!ref.isPeeled() || ref.getPeeledObjectId() != null)) { + ed.add(new DeletePath(peeledPath(ref.getName()))); + } + } + + private static boolean abort(Iterable cmdList) { + for (Command cmd : cmdList) { + if (cmd.getResult() == NOT_ATTEMPTED) { + reject(cmd, JGitText.get().transactionAborted); + } + } + return false; + } + + private static void reject(Command cmd, String msg) { + cmd.setResult(REJECTED_OTHER_REASON, msg); + } + + /** + * Convert a path name in a RefTree to the reference name known by Git. + * + * @param path + * name read from the RefTree structure, for example + * {@code "heads/master"}. + * @return reference name for the path, {@code "refs/heads/master"}. + */ + public static String refName(String path) { + if (path.startsWith(ROOT_DOTDOT)) { + return path.substring(2); + } + return R_REFS + path; + } + + private static String refPath(String name) { + if (name.startsWith(R_REFS)) { + return name.substring(R_REFS.length()); + } + return ROOT_DOTDOT + name; + } + + private static String peeledPath(String name) { + return refPath(name) + PEELED_SUFFIX; + } + + /** + * Write this reference tree. + * + * @param inserter + * inserter to use when writing trees to the object database. + * Caller is responsible for flushing the inserter before trying + * to read the objects, or exposing them through a reference. + * @return the top level tree. + * @throws IOException + * a tree could not be written. + */ + public ObjectId writeTree(ObjectInserter inserter) throws IOException { + if (pendingBlobs != null) { + for (String s : pendingBlobs.values()) { + inserter.insert(OBJ_BLOB, encode(s)); + } + pendingBlobs = null; + } + return contents.writeTree(inserter); + } + + /** @return a deep copy of this RefTree. */ + public RefTree copy() { + RefTree r = new RefTree(DirCache.newInCore()); + DirCacheBuilder b = r.contents.builder(); + for (int i = 0; i < contents.getEntryCount(); i++) { + b.add(new DirCacheEntry(contents.getEntry(i))); + } + b.finish(); + if (pendingBlobs != null) { + r.pendingBlobs = new HashMap<>(pendingBlobs); + } + return r; + } + + private static class LockFailureException extends RuntimeException { + private static final long serialVersionUID = 1L; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java index 986666f2f..c0c3862c8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java @@ -82,8 +82,10 @@ public abstract class RefDatabase { *

* If the reference is nested deeper than this depth, the implementation * should either fail, or at least claim the reference does not exist. + * + * @since 4.2 */ - protected static final int MAX_SYMBOLIC_REF_DEPTH = 5; + public static final int MAX_SYMBOLIC_REF_DEPTH = 5; /** Magic value for {@link #getRefs(String)} to return all references. */ public static final String ALL = "";//$NON-NLS-1$ From 52eb040c559345777592b28187d4b0753505323e Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 27 Nov 2015 19:34:36 -0800 Subject: [PATCH 68/89] debug-rebuild-ref-tree: Simple program to build a RefTree This tool scans all references in the repository and writes out a new reference pointing to a single commit whose root tree is a RefTree containing the current refs of this repository. It alway skips storing the reference it will write to, avoiding the obvious cycle. Change-Id: I20b1eeb81c55dc49dd600eac3bf8f90297394113 --- org.eclipse.jgit.pgm/META-INF/MANIFEST.MF | 1 + .../services/org.eclipse.jgit.pgm.TextBuiltin | 1 + .../jgit/pgm/internal/CLIText.properties | 2 + .../jgit/pgm/debug/RebuildRefTree.java | 130 ++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF index 9cbd377fe..9dc6aea16 100644 --- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF @@ -21,6 +21,7 @@ Import-Package: org.apache.commons.compress.archivers;version="[1.3,2.0)", org.eclipse.jgit.gitrepo;version="[4.2.0,4.3.0)", org.eclipse.jgit.internal.storage.file;version="[4.2.0,4.3.0)", org.eclipse.jgit.internal.storage.pack;version="[4.2.0,4.3.0)", + org.eclipse.jgit.internal.storage.reftree;version="[4.2.0,4.3.0)", org.eclipse.jgit.lib;version="[4.2.0,4.3.0)", org.eclipse.jgit.merge;version="4.2.0", org.eclipse.jgit.nls;version="[4.2.0,4.3.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 c13f63e80..6aa20041b 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 @@ -41,6 +41,7 @@ org.eclipse.jgit.pgm.debug.DiffAlgorithms org.eclipse.jgit.pgm.debug.MakeCacheTree org.eclipse.jgit.pgm.debug.ReadDirCache org.eclipse.jgit.pgm.debug.RebuildCommitGraph +org.eclipse.jgit.pgm.debug.RebuildRefTree org.eclipse.jgit.pgm.debug.ShowCacheTree org.eclipse.jgit.pgm.debug.ShowCommands org.eclipse.jgit.pgm.debug.ShowDirCache diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties index a24b81794..b4b1261b3 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties @@ -228,6 +228,7 @@ usage_MergeBase=Find as good common ancestors as possible for a merge usage_MergesTwoDevelopmentHistories=Merges two development histories usage_ReadDirCache= Read the DirCache 100 times usage_RebuildCommitGraph=Recreate a repository from another one's commit graph +usage_RebuildRefTree=Copy references into a RefTree usage_Remote=Manage set of tracked repositories usage_RepositoryToReadFrom=Repository to read from usage_RepositoryToReceiveInto=Repository to receive into @@ -359,6 +360,7 @@ usage_tags=fetch all tags usage_notags=do not fetch tags usage_tagMessage=tag message usage_untrackedFilesMode=show untracked files +usage_updateRef=reference to update usage_updateRemoteRefsFromAnotherRepository=Update remote refs from another repository usage_useNameInsteadOfOriginToTrackUpstream=use instead of 'origin' to track upstream usage_checkoutBranchAfterClone=checkout named branch instead of remotes's HEAD diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java new file mode 100644 index 000000000..6af4ce1dd --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2015, Google Inc. + * 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.debug; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.internal.storage.reftree.RefTree; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.pgm.Command; +import org.eclipse.jgit.pgm.TextBuiltin; +import org.eclipse.jgit.revwalk.RevWalk; +import org.kohsuke.args4j.Argument; + +@Command(usage = "usage_RebuildRefTree") +class RebuildRefTree extends TextBuiltin { + @Argument(index = 0, required = true, metaVar = "metaVar_ref", usage = "usage_updateRef") + String refName; + + @Override + protected void run() throws Exception { + try (ObjectReader reader = db.newObjectReader(); + RevWalk rw = new RevWalk(reader); + ObjectInserter inserter = db.newObjectInserter()) { + RefDatabase refDb = db.getRefDatabase(); + + CommitBuilder b = new CommitBuilder(); + Ref ref = db.getRefDatabase().exactRef(refName); + RefUpdate update = db.updateRef(refName); + ObjectId oldTreeId; + + if (ref != null && ref.getObjectId() != null) { + ObjectId oldId = ref.getObjectId(); + update.setExpectedOldObjectId(oldId); + b.setParentId(oldId); + oldTreeId = rw.parseCommit(oldId).getTree(); + } else { + update.setExpectedOldObjectId(ObjectId.zeroId()); + oldTreeId = ObjectId.zeroId(); + } + + RefTree tree = rebuild(refDb.getRefs(RefDatabase.ALL)); + b.setTreeId(tree.writeTree(inserter)); + b.setAuthor(new PersonIdent(db)); + b.setCommitter(b.getAuthor()); + if (b.getTreeId().equals(oldTreeId)) { + return; + } + + update.setNewObjectId(inserter.insert(b)); + inserter.flush(); + + RefUpdate.Result result = update.update(rw); + switch (result) { + case NEW: + case FAST_FORWARD: + break; + default: + throw die(String.format("%s: %s", update.getName(), result)); //$NON-NLS-1$ + } + } + } + + private RefTree rebuild(Map refMap) { + RefTree tree = RefTree.newEmptyTree(); + List cmds + = new ArrayList<>(); + + for (Ref r : refMap.values()) { + if (refName.equals(r.getName())) { + continue; + } + + cmds.add(new org.eclipse.jgit.internal.storage.reftree.Command( + null, + db.peel(r))); + } + tree.apply(cmds); + return tree; + } +} From bace3835073b16a9d005d5baa88421071f433e4e Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 8 Jan 2016 16:56:47 -0800 Subject: [PATCH 69/89] Add Paths utility class Simple container for some path related utility functions. Change-Id: Ice2bec6ad12b1e2cea15988c01aa9dd4e016a849 --- .../tst/org/eclipse/jgit/util/PathsTest.java | 62 +++++++++++++++ .../src/org/eclipse/jgit/util/Paths.java | 77 +++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java new file mode 100644 index 000000000..090f3d940 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +public class PathsTest { + @Test + public void testStripTrailingSeparator() { + assertNull(Paths.stripTrailingSeparator(null)); + assertEquals("", Paths.stripTrailingSeparator("")); + assertEquals("a", Paths.stripTrailingSeparator("a")); + assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo")); + assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo/")); + assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo//")); + assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo///")); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java new file mode 100644 index 000000000..ce2cbc4d3 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.util; + +/** + * Utility functions for paths inside of a Git repository. + * + * @since 4.2 + */ +public class Paths { + /** + * Remove trailing {@code '/'} if present. + * + * @param path + * input path to potentially remove trailing {@code '/'} from. + * @return null if {@code path == null}; {@code path} after removing a + * trailing {@code '/'}. + */ + public static String stripTrailingSeparator(String path) { + if (path == null || path.isEmpty()) { + return path; + } + + int i = path.length(); + if (path.charAt(path.length() - 1) != '/') { + return path; + } + do { + i--; + } while (path.charAt(i - 1) == '/'); + return path.substring(0, i); + } + + private Paths() { + } +} From 1243e25aadfb810bb39127473e80b3a5cb761883 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 8 Jan 2016 17:28:44 -0800 Subject: [PATCH 70/89] Paths.pathCompare: Utility to sort paths from byte[] Consolidate copies of this function into one location. Add some unit tests to prevent bugs that were accidentally introduced while trying to make this refactoring. Change-Id: I82f64bbb8601ca2d8316ca57ae8119df32bb5c08 --- .../jgit/dircache/DirCachePathEditTest.java | 17 +++ .../eclipse/jgit/lib/ObjectCheckerTest.java | 16 ++- .../tst/org/eclipse/jgit/util/PathsTest.java | 56 +++++++++ .../jgit/dircache/BaseDirCacheEditor.java | 28 +---- .../eclipse/jgit/dircache/DirCacheEditor.java | 3 +- .../org/eclipse/jgit/lib/ObjectChecker.java | 31 ++--- .../org/eclipse/jgit/notes/NonNoteEntry.java | 26 +--- .../jgit/treewalk/AbstractTreeIterator.java | 22 +--- .../jgit/treewalk/WorkingTreeIterator.java | 27 +---- .../src/org/eclipse/jgit/util/Paths.java | 111 ++++++++++++++++++ 10 files changed, 225 insertions(+), 112 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java index 39a0cdac5..c85e15635 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCachePathEditTest.java @@ -214,6 +214,23 @@ public void testTreeReplacesFile() throws Exception { assertEquals("e", dc.getEntry(4).getPathString()); } + @Test + public void testDuplicateFiles() throws Exception { + DirCache dc = DirCache.newInCore(); + DirCacheEditor editor = dc.editor(); + editor.add(new AddEdit("a")); + editor.add(new AddEdit("a")); + + try { + editor.finish(); + fail("Expected DirCacheNameConflictException to be thrown"); + } catch (DirCacheNameConflictException e) { + assertEquals("a a", e.getMessage()); + assertEquals("a", e.getPath1()); + assertEquals("a", e.getPath2()); + } + } + @Test public void testFileOverlapsTree() throws Exception { DirCache dc = DirCache.newInCore(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java index 80230dccf..43160fb11 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java @@ -1327,7 +1327,8 @@ public void testInvalidTreeBadSorting3() throws CorruptObjectException { } @Test - public void testInvalidTreeDuplicateNames1() throws CorruptObjectException { + public void testInvalidTreeDuplicateNames1_File() + throws CorruptObjectException { StringBuilder b = new StringBuilder(); entry(b, "100644 a"); entry(b, "100644 a"); @@ -1338,6 +1339,19 @@ public void testInvalidTreeDuplicateNames1() throws CorruptObjectException { checker.checkTree(data); } + @Test + public void testInvalidTreeDuplicateNames1_Tree() + throws CorruptObjectException { + StringBuilder b = new StringBuilder(); + entry(b, "40000 a"); + entry(b, "40000 a"); + byte[] data = encodeASCII(b.toString()); + assertCorrupt("duplicate entry names", OBJ_TREE, data); + assertSkipListAccepts(OBJ_TREE, data); + checker.setIgnore(DUPLICATE_ENTRIES, true); + checker.checkTree(data); + } + @Test public void testInvalidTreeDuplicateNames2() throws CorruptObjectException { StringBuilder b = new StringBuilder(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java index 090f3d940..7542ec891 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/PathsTest.java @@ -43,9 +43,13 @@ package org.eclipse.jgit.util; +import static org.eclipse.jgit.util.Paths.compare; +import static org.eclipse.jgit.util.Paths.compareSameName; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; import org.junit.Test; public class PathsTest { @@ -59,4 +63,56 @@ public void testStripTrailingSeparator() { assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo//")); assertEquals("a/boo", Paths.stripTrailingSeparator("a/boo///")); } + + @Test + public void testPathCompare() { + byte[] a = Constants.encode("afoo/bar.c"); + byte[] b = Constants.encode("bfoo/bar.c"); + + assertEquals(0, compare(a, 1, a.length, 0, b, 1, b.length, 0)); + assertEquals(-1, compare(a, 0, a.length, 0, b, 0, b.length, 0)); + assertEquals(1, compare(b, 0, b.length, 0, a, 0, a.length, 0)); + + a = Constants.encode("a"); + b = Constants.encode("aa"); + assertEquals(-97, compare(a, 0, a.length, 0, b, 0, b.length, 0)); + assertEquals(0, compare(a, 0, a.length, 0, b, 0, 1, 0)); + assertEquals(0, compare(a, 0, a.length, 0, b, 1, 2, 0)); + assertEquals(0, compareSameName(a, 0, a.length, b, 1, b.length, 0)); + assertEquals(0, compareSameName(a, 0, a.length, b, 0, 1, 0)); + assertEquals(-50, compareSameName(a, 0, a.length, b, 0, b.length, 0)); + assertEquals(97, compareSameName(b, 0, b.length, a, 0, a.length, 0)); + + a = Constants.encode("a"); + b = Constants.encode("a"); + assertEquals(0, compare( + a, 0, a.length, FileMode.TREE.getBits(), + b, 0, b.length, FileMode.TREE.getBits())); + assertEquals(0, compare( + a, 0, a.length, FileMode.REGULAR_FILE.getBits(), + b, 0, b.length, FileMode.REGULAR_FILE.getBits())); + assertEquals(-47, compare( + a, 0, a.length, FileMode.REGULAR_FILE.getBits(), + b, 0, b.length, FileMode.TREE.getBits())); + assertEquals(47, compare( + a, 0, a.length, FileMode.TREE.getBits(), + b, 0, b.length, FileMode.REGULAR_FILE.getBits())); + + assertEquals(0, compareSameName( + a, 0, a.length, + b, 0, b.length, FileMode.TREE.getBits())); + assertEquals(0, compareSameName( + a, 0, a.length, + b, 0, b.length, FileMode.REGULAR_FILE.getBits())); + + a = Constants.encode("a.c"); + b = Constants.encode("a"); + byte[] c = Constants.encode("a0c"); + assertEquals(-1, compare( + a, 0, a.length, FileMode.REGULAR_FILE.getBits(), + b, 0, b.length, FileMode.TREE.getBits())); + assertEquals(-1, compare( + b, 0, b.length, FileMode.TREE.getBits(), + c, 0, c.length, FileMode.REGULAR_FILE.getBits())); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java index fc789b3d1..0fbc1f8ac 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java @@ -44,8 +44,8 @@ package org.eclipse.jgit.dircache; -import static org.eclipse.jgit.lib.FileMode.TREE; import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; +import static org.eclipse.jgit.util.Paths.compareSameName; import java.io.IOException; @@ -207,8 +207,8 @@ private void checkNameConflicts() { int s = nextSlash(nPath, prefixLen); int m = s < nPath.length ? TYPE_TREE : n.getRawMode(); - int cmp = pathCompare( - ePath, prefixLen, ePath.length, TYPE_TREE, + int cmp = compareSameName( + ePath, prefixLen, ePath.length, nPath, prefixLen, s, m); if (cmp < 0) { break; @@ -252,28 +252,6 @@ private static boolean startsWith(byte[] a, byte[] b, int n) { return true; } - static int pathCompare(byte[] aPath, int aPos, int aEnd, int aMode, - byte[] bPath, int bPos, int bEnd, int bMode) { - while (aPos < aEnd && bPos < bEnd) { - int cmp = (aPath[aPos++] & 0xff) - (bPath[bPos++] & 0xff); - if (cmp != 0) { - return cmp; - } - } - - if (aPos < aEnd) { - return (aPath[aPos] & 0xff) - lastPathChar(bMode); - } - if (bPos < bEnd) { - return lastPathChar(aMode) - (bPath[bPos] & 0xff); - } - return 0; - } - - private static int lastPathChar(int mode) { - return TREE.equals(mode) ? '/' : '\0'; - } - /** * Finish, write, commit this change, and release the index lock. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java index 889e19422..c987c964c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java @@ -57,6 +57,7 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.util.Paths; /** * Updates a {@link DirCache} by supplying discrete edit commands. @@ -215,7 +216,7 @@ private int deleteOverlappingSubtree(DirCacheEntry ent, int eIdx) { } DirCacheEntry next = cache.getEntry(eIdx); - if (pathCompare(next.path, 0, next.path.length, 0, + if (Paths.compare(next.path, 0, next.path.length, 0, entPath, 0, entLen, TYPE_TREE) < 0) { // Next DirCacheEntry sorts before new entry as tree. Defer a // DeleteTree command to delete any entries if they exist. This diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java index 858a0385d..0b5efd77d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java @@ -77,6 +77,8 @@ import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.UNKNOWN_TYPE; import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.WIN32_BAD_NAME; import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE; +import static org.eclipse.jgit.util.Paths.compare; +import static org.eclipse.jgit.util.Paths.compareSameName; import static org.eclipse.jgit.util.RawParseUtils.nextLF; import static org.eclipse.jgit.util.RawParseUtils.parseBase10; @@ -541,25 +543,6 @@ public void checkTag(@Nullable AnyObjectId id, byte[] raw) } } - private static int lastPathChar(final int mode) { - return FileMode.TREE.equals(mode) ? '/' : '\0'; - } - - private static int pathCompare(final byte[] raw, int aPos, final int aEnd, - final int aMode, int bPos, final int bEnd, final int bMode) { - while (aPos < aEnd && bPos < bEnd) { - final int cmp = (raw[aPos++] & 0xff) - (raw[bPos++] & 0xff); - if (cmp != 0) - return cmp; - } - - if (aPos < aEnd) - return (raw[aPos] & 0xff) - lastPathChar(bMode); - if (bPos < bEnd) - return lastPathChar(aMode) - (raw[bPos] & 0xff); - return 0; - } - private static boolean duplicateName(final byte[] raw, final int thisNamePos, final int thisNameEnd) { final int sz = raw.length; @@ -587,8 +570,9 @@ private static boolean duplicateName(final byte[] raw, if (nextNamePos + 1 == nextPtr) return false; - final int cmp = pathCompare(raw, thisNamePos, thisNameEnd, - FileMode.TREE.getBits(), nextNamePos, nextPtr - 1, nextMode); + int cmp = compareSameName( + raw, thisNamePos, thisNameEnd, + raw, nextNamePos, nextPtr - 1, nextMode); if (cmp < 0) return false; else if (cmp == 0) @@ -676,8 +660,9 @@ public void checkTree(@Nullable AnyObjectId id, byte[] raw) } if (lastNameB != 0) { - final int cmp = pathCompare(raw, lastNameB, lastNameE, - lastMode, thisNameB, ptr, thisMode); + int cmp = compare( + raw, lastNameB, lastNameE, lastMode, + raw, thisNameB, ptr, thisMode); if (cmp > 0) { report(TREE_NOT_SORTED, id, JGitText.get().corruptObjectIncorrectSorting); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java index 6a2d44bca..362328a96 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java @@ -47,6 +47,7 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.TreeFormatter; +import org.eclipse.jgit.util.Paths; /** A tree entry found in a note branch that isn't a valid note. */ class NonNoteEntry extends ObjectId { @@ -74,27 +75,8 @@ int treeEntrySize() { } int pathCompare(byte[] bBuf, int bPos, int bLen, FileMode bMode) { - return pathCompare(name, 0, name.length, mode, // - bBuf, bPos, bLen, bMode); - } - - private static int pathCompare(final byte[] aBuf, int aPos, final int aEnd, - final FileMode aMode, final byte[] bBuf, int bPos, final int bEnd, - final FileMode bMode) { - while (aPos < aEnd && bPos < bEnd) { - int cmp = (aBuf[aPos++] & 0xff) - (bBuf[bPos++] & 0xff); - if (cmp != 0) - return cmp; - } - - if (aPos < aEnd) - return (aBuf[aPos] & 0xff) - lastPathChar(bMode); - if (bPos < bEnd) - return lastPathChar(aMode) - (bBuf[bPos] & 0xff); - return 0; - } - - private static int lastPathChar(final FileMode mode) { - return FileMode.TREE.equals(mode.getBits()) ? '/' : '\0'; + return Paths.compare( + name, 0, name.length, mode.getBits(), + bBuf, bPos, bLen, bMode.getBits()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java index 27d0f7b58..58136355e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java @@ -59,6 +59,7 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.eclipse.jgit.util.Paths; /** * Walks a Git tree (directory) in Git sort order. @@ -382,20 +383,9 @@ public int pathCompare(byte[] buf, int pos, int end, int pathMode) { } private int pathCompare(byte[] b, int bPos, int bEnd, int bMode, int aPos) { - final byte[] a = path; - final int aEnd = pathLen; - - for (; aPos < aEnd && bPos < bEnd; aPos++, bPos++) { - final int cmp = (a[aPos] & 0xff) - (b[bPos] & 0xff); - if (cmp != 0) - return cmp; - } - - if (aPos < aEnd) - return (a[aPos] & 0xff) - lastPathChar(bMode); - if (bPos < bEnd) - return lastPathChar(mode) - (b[bPos] & 0xff); - return lastPathChar(mode) - lastPathChar(bMode); + return Paths.compare( + path, aPos, pathLen, mode, + b, bPos, bEnd, bMode); } private static int alreadyMatch(AbstractTreeIterator a, @@ -412,10 +402,6 @@ private static int alreadyMatch(AbstractTreeIterator a, } } - private static int lastPathChar(final int mode) { - return FileMode.TREE.equals(mode) ? '/' : '\0'; - } - /** * Check if the current entry of both iterators has the same id. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 94beeeb56..0d617ee7f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -89,6 +89,7 @@ import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.IO; +import org.eclipse.jgit.util.Paths; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.io.EolCanonicalizingInputStream; @@ -692,31 +693,13 @@ public AttributesNode getEntryAttributesNode() throws IOException { } private static final Comparator ENTRY_CMP = new Comparator() { - public int compare(final Entry o1, final Entry o2) { - final byte[] a = o1.encodedName; - final byte[] b = o2.encodedName; - final int aLen = o1.encodedNameLen; - final int bLen = o2.encodedNameLen; - int cPos; - - for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) { - final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff); - if (cmp != 0) - return cmp; - } - - if (cPos < aLen) - return (a[cPos] & 0xff) - lastPathChar(o2); - if (cPos < bLen) - return lastPathChar(o1) - (b[cPos] & 0xff); - return lastPathChar(o1) - lastPathChar(o2); + public int compare(Entry a, Entry b) { + return Paths.compare( + a.encodedName, 0, a.encodedNameLen, a.getMode().getBits(), + b.encodedName, 0, b.encodedNameLen, b.getMode().getBits()); } }; - static int lastPathChar(final Entry e) { - return e.getMode() == FileMode.TREE ? '/' : '\0'; - } - /** * Constructor helper. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java index ce2cbc4d3..6be7ddbe1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java @@ -43,6 +43,9 @@ package org.eclipse.jgit.util; +import static org.eclipse.jgit.lib.FileMode.TYPE_MASK; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; + /** * Utility functions for paths inside of a Git repository. * @@ -72,6 +75,114 @@ public static String stripTrailingSeparator(String path) { return path.substring(0, i); } + /** + * Compare two paths according to Git path sort ordering rules. + * + * @param aPath + * first path buffer. The range {@code [aPos, aEnd)} is used. + * @param aPos + * index into {@code aPath} where the first path starts. + * @param aEnd + * 1 past last index of {@code aPath}. + * @param aMode + * mode of the first file. Trees are sorted as though + * {@code aPath[aEnd] == '/'}, even if aEnd does not exist. + * @param bPath + * second path buffer. The range {@code [bPos, bEnd)} is used. + * @param bPos + * index into {@code bPath} where the second path starts. + * @param bEnd + * 1 past last index of {@code bPath}. + * @param bMode + * mode of the second file. Trees are sorted as though + * {@code bPath[bEnd] == '/'}, even if bEnd does not exist. + * @return <0 if {@code aPath} sorts before {@code bPath}; + * 0 if the paths are the same; + * >0 if {@code aPath} sorts after {@code bPath}. + */ + public static int compare(byte[] aPath, int aPos, int aEnd, int aMode, + byte[] bPath, int bPos, int bEnd, int bMode) { + int cmp = coreCompare( + aPath, aPos, aEnd, aMode, + bPath, bPos, bEnd, bMode); + if (cmp == 0) { + cmp = lastPathChar(aMode) - lastPathChar(bMode); + } + return cmp; + } + + /** + * Compare two paths, checking for identical name. + *

+ * Unlike {@code compare} this method returns {@code 0} when the paths have + * the same characters in their names, even if the mode differs. It is + * intended for use in validation routines detecting duplicate entries. + *

+ * Returns {@code 0} if the names are identical and a conflict exists + * between {@code aPath} and {@code bPath}, as they share the same name. + *

+ * Returns {@code <0} if all possibles occurrences of {@code aPath} sort + * before {@code bPath} and no conflict can happen. In a properly sorted + * tree there are no other occurrences of {@code aPath} and therefore there + * are no duplicate names. + *

+ * Returns {@code >0} when it is possible for a duplicate occurrence of + * {@code aPath} to appear later, after {@code bPath}. Callers should + * continue to examine candidates for {@code bPath} until the method returns + * one of the other return values. + * + * @param aPath + * first path buffer. The range {@code [aPos, aEnd)} is used. + * @param aPos + * index into {@code aPath} where the first path starts. + * @param aEnd + * 1 past last index of {@code aPath}. + * @param bPath + * second path buffer. The range {@code [bPos, bEnd)} is used. + * @param bPos + * index into {@code bPath} where the second path starts. + * @param bEnd + * 1 past last index of {@code bPath}. + * @param bMode + * mode of the second file. Trees are sorted as though + * {@code bPath[bEnd] == '/'}, even if bEnd does not exist. + * @return <0 if no duplicate name could exist; + * 0 if the paths have the same name; + * >0 other {@code bPath} should still be checked by caller. + */ + public static int compareSameName( + byte[] aPath, int aPos, int aEnd, + byte[] bPath, int bPos, int bEnd, int bMode) { + return coreCompare( + aPath, aPos, aEnd, TYPE_TREE, + bPath, bPos, bEnd, bMode); + } + + private static int coreCompare( + byte[] aPath, int aPos, int aEnd, int aMode, + byte[] bPath, int bPos, int bEnd, int bMode) { + while (aPos < aEnd && bPos < bEnd) { + int cmp = (aPath[aPos++] & 0xff) - (bPath[bPos++] & 0xff); + if (cmp != 0) { + return cmp; + } + } + if (aPos < aEnd) { + return (aPath[aPos] & 0xff) - lastPathChar(bMode); + } + if (bPos < bEnd) { + return lastPathChar(aMode) - (bPath[bPos] & 0xff); + } + return 0; + } + + private static int lastPathChar(int mode) { + if ((mode & TYPE_MASK) == TYPE_TREE) { + return '/'; + } + return 0; + } + private Paths() { } } From 0f8743d4d7a4f3af1eccea60d45d51d13f1a2ad4 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 8 Jan 2016 21:42:07 -0800 Subject: [PATCH 71/89] Remove deprecated Tree, TreeEntry, FileTreeEntry and friends These types were deprecated in 0.9.1 (aka 384a19eee07a2f). If anyone is still using them, its time to stop. Change-Id: I3f73347ba78c639e0c6a504812bc1a0702f829b1 --- .../storage/file/T0003_BasicTest.java | 53 +- .../org/eclipse/jgit/lib/IndexDiffTest.java | 91 ++- .../org/eclipse/jgit/lib/T0002_TreeTest.java | 319 ---------- .../eclipse/jgit/revwalk/ObjectWalkTest.java | 39 +- .../jgit/treewalk/TreeWalkBasicDiffTest.java | 77 ++- .../org/eclipse/jgit/lib/FileTreeEntry.java | 115 ---- .../eclipse/jgit/lib/GitlinkTreeEntry.java | 87 --- .../eclipse/jgit/lib/SymlinkTreeEntry.java | 85 --- .../src/org/eclipse/jgit/lib/Tree.java | 601 ------------------ .../src/org/eclipse/jgit/lib/TreeEntry.java | 256 -------- 10 files changed, 108 insertions(+), 1615 deletions(-) delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java index 5670a9634..a100ff357 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java @@ -67,7 +67,6 @@ import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.FileTreeEntry; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; @@ -75,7 +74,6 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.TagBuilder; -import org.eclipse.jgit.lib.Tree; import org.eclipse.jgit.lib.TreeFormatter; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTag; @@ -418,29 +416,6 @@ public void test009_CreateCommitOldFormat() throws IOException { assertEquals(c.getCommitter(), c2.getCommitterIdent()); } - @Test - public void test012_SubtreeExternalSorting() throws IOException { - final ObjectId emptyBlob = insertEmptyBlob(); - final Tree t = new Tree(db); - final FileTreeEntry e0 = t.addFile("a-"); - final FileTreeEntry e1 = t.addFile("a-b"); - final FileTreeEntry e2 = t.addFile("a/b"); - final FileTreeEntry e3 = t.addFile("a="); - final FileTreeEntry e4 = t.addFile("a=b"); - - e0.setId(emptyBlob); - e1.setId(emptyBlob); - e2.setId(emptyBlob); - e3.setId(emptyBlob); - e4.setId(emptyBlob); - - final Tree a = (Tree) t.findTreeMember("a"); - a.setId(insertTree(a)); - assertEquals(ObjectId - .fromString("b47a8f0a4190f7572e11212769090523e23eb1ea"), - insertTree(t)); - } - @Test public void test020_createBlobTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); @@ -464,9 +439,8 @@ public void test020_createBlobTag() throws IOException { @Test public void test021_createTreeTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); final TagBuilder t = new TagBuilder(); t.setObjectId(almostEmptyTreeId, Constants.OBJ_TREE); @@ -488,9 +462,8 @@ public void test021_createTreeTag() throws IOException { @Test public void test022_createCommitTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); final CommitBuilder almostEmptyCommit = new CommitBuilder(); almostEmptyCommit.setAuthor(new PersonIdent(author, 1154236443000L, @@ -520,9 +493,8 @@ public void test022_createCommitTag() throws IOException { @Test public void test023_createCommitNonAnullii() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); CommitBuilder commit = new CommitBuilder(); commit.setTreeId(almostEmptyTreeId); @@ -542,9 +514,8 @@ public void test023_createCommitNonAnullii() throws IOException { @Test public void test024_createCommitNonAscii() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); CommitBuilder commit = new CommitBuilder(); commit.setTreeId(almostEmptyTreeId); @@ -746,14 +717,6 @@ private ObjectId insertEmptyBlob() throws IOException { return emptyId; } - private ObjectId insertTree(Tree tree) throws IOException { - try (ObjectInserter oi = db.newObjectInserter()) { - ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format()); - oi.flush(); - return id; - } - } - private ObjectId insertTree(TreeFormatter tree) throws IOException { try (ObjectInserter oi = db.newObjectInserter()) { ObjectId id = oi.insert(tree); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java index a5cd7b5c0..7fcee3dc8 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java @@ -99,8 +99,7 @@ public void apply(DirCacheEntry ent) { public void testAdded() throws IOException { writeTrashFile("file1", "file1"); writeTrashFile("dir/subfile", "dir/subfile"); - Tree tree = new Tree(db); - tree.setId(insertTree(tree)); + ObjectId tree = insertTree(new TreeFormatter()); DirCache index = db.lockDirCache(); DirCacheEditor editor = index.editor(); @@ -108,7 +107,7 @@ public void testAdded() throws IOException { editor.add(add(db, trash, "dir/subfile")); editor.commit(); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, tree, iterator); diff.diff(); assertEquals(2, diff.getAdded().size()); assertTrue(diff.getAdded().contains("file1")); @@ -124,18 +123,16 @@ public void testRemoved() throws IOException { writeTrashFile("file2", "file2"); writeTrashFile("dir/file3", "dir/file3"); - Tree tree = new Tree(db); - tree.addFile("file2"); - tree.addFile("dir/file3"); - assertEquals(2, tree.memberCount()); - tree.findBlobMember("file2").setId(ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad")); - Tree tree2 = (Tree) tree.findTreeMember("dir"); - tree2.findBlobMember("file3").setId(ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b")); - tree2.setId(insertTree(tree2)); - tree.setId(insertTree(tree)); + TreeFormatter dir = new TreeFormatter(); + dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b")); + + TreeFormatter tree = new TreeFormatter(); + tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad")); + tree.append("dir", FileMode.TREE, insertTree(dir)); + ObjectId treeId = insertTree(tree); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(2, diff.getRemoved().size()); assertTrue(diff.getRemoved().contains("file2")); @@ -157,16 +154,16 @@ public void testModified() throws IOException, GitAPIException { writeTrashFile("dir/file3", "changed"); - Tree tree = new Tree(db); - tree.addFile("file2").setId(ObjectId.fromString("0123456789012345678901234567890123456789")); - tree.addFile("dir/file3").setId(ObjectId.fromString("0123456789012345678901234567890123456789")); - assertEquals(2, tree.memberCount()); + TreeFormatter dir = new TreeFormatter(); + dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789")); + + TreeFormatter tree = new TreeFormatter(); + tree.append("dir", FileMode.TREE, insertTree(dir)); + tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789")); + ObjectId treeId = insertTree(tree); - Tree tree2 = (Tree) tree.findTreeMember("dir"); - tree2.setId(insertTree(tree2)); - tree.setId(insertTree(tree)); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(2, diff.getChanged().size()); assertTrue(diff.getChanged().contains("file2")); @@ -314,17 +311,16 @@ public void testUnchangedSimple() throws IOException, GitAPIException { git.add().addFilepattern("a=c").call(); git.add().addFilepattern("a=d").call(); - Tree tree = new Tree(db); + TreeFormatter tree = new TreeFormatter(); // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin - tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); - tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); - tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); - tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); - - tree.setId(insertTree(tree)); + tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); + tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); + tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); + tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); + ObjectId treeId = insertTree(tree); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getAdded().size()); @@ -356,24 +352,27 @@ public void testUnchangedComplex() throws IOException, GitAPIException { .addFilepattern("a/c").addFilepattern("a=c") .addFilepattern("a=d").call(); - Tree tree = new Tree(db); - // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin - tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); - tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); - tree.addFile("a/b.b/b").setId(ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd")); - tree.addFile("a/b").setId(ObjectId.fromString("db89c972fc57862eae378f45b74aca228037d415")); - tree.addFile("a/c").setId(ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007")); - tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); - tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); - Tree tree3 = (Tree) tree.findTreeMember("a/b.b"); - tree3.setId(insertTree(tree3)); - Tree tree2 = (Tree) tree.findTreeMember("a"); - tree2.setId(insertTree(tree2)); - tree.setId(insertTree(tree)); + // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin + TreeFormatter bb = new TreeFormatter(); + bb.append("b", FileMode.REGULAR_FILE, ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd")); + + TreeFormatter a = new TreeFormatter(); + a.append("b", FileMode.REGULAR_FILE, ObjectId + .fromString("db89c972fc57862eae378f45b74aca228037d415")); + a.append("b.b", FileMode.TREE, insertTree(bb)); + a.append("c", FileMode.REGULAR_FILE, ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007")); + + TreeFormatter tree = new TreeFormatter(); + tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); + tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); + tree.append("a", FileMode.TREE, insertTree(a)); + tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); + tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); + ObjectId treeId = insertTree(tree); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getAdded().size()); @@ -383,9 +382,9 @@ public void testUnchangedComplex() throws IOException, GitAPIException { assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } - private ObjectId insertTree(Tree tree) throws IOException { + private ObjectId insertTree(TreeFormatter tree) throws IOException { try (ObjectInserter oi = db.newObjectInserter()) { - ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format()); + ObjectId id = oi.insert(tree); oi.flush(); return id; } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java deleted file mode 100644 index 651e62c9c..000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (C) 2008, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; -import org.junit.Test; - -@SuppressWarnings("deprecation") -public class T0002_TreeTest extends SampleDataRepositoryTestCase { - private static final ObjectId SOME_FAKE_ID = ObjectId.fromString( - "0123456789abcdef0123456789abcdef01234567"); - - private static int compareNamesUsingSpecialCompare(String a, String b) - throws UnsupportedEncodingException { - char lasta = '\0'; - byte[] abytes; - if (a.length() > 0 && a.charAt(a.length()-1) == '/') { - lasta = '/'; - a = a.substring(0, a.length() - 1); - } - abytes = a.getBytes("ISO-8859-1"); - char lastb = '\0'; - byte[] bbytes; - if (b.length() > 0 && b.charAt(b.length()-1) == '/') { - lastb = '/'; - b = b.substring(0, b.length() - 1); - } - bbytes = b.getBytes("ISO-8859-1"); - return Tree.compareNames(abytes, bbytes, lasta, lastb); - } - - @Test - public void test000_sort_01() throws UnsupportedEncodingException { - assertEquals(0, compareNamesUsingSpecialCompare("a","a")); - } - - @Test - public void test000_sort_02() throws UnsupportedEncodingException { - assertEquals(-1, compareNamesUsingSpecialCompare("a","b")); - assertEquals(1, compareNamesUsingSpecialCompare("b","a")); - } - - @Test - public void test000_sort_03() throws UnsupportedEncodingException { - assertEquals(1, compareNamesUsingSpecialCompare("a:","a")); - assertEquals(1, compareNamesUsingSpecialCompare("a/","a")); - assertEquals(-1, compareNamesUsingSpecialCompare("a","a/")); - assertEquals(-1, compareNamesUsingSpecialCompare("a","a:")); - assertEquals(1, compareNamesUsingSpecialCompare("a:","a/")); - assertEquals(-1, compareNamesUsingSpecialCompare("a/","a:")); - } - - @Test - public void test000_sort_04() throws UnsupportedEncodingException { - assertEquals(-1, compareNamesUsingSpecialCompare("a.a","a/a")); - assertEquals(1, compareNamesUsingSpecialCompare("a/a","a.a")); - } - - @Test - public void test000_sort_05() throws UnsupportedEncodingException { - assertEquals(-1, compareNamesUsingSpecialCompare("a.","a/")); - assertEquals(1, compareNamesUsingSpecialCompare("a/","a.")); - - } - - @Test - public void test001_createEmpty() throws IOException { - final Tree t = new Tree(db); - assertTrue("isLoaded", t.isLoaded()); - assertTrue("isModified", t.isModified()); - assertTrue("no parent", t.getParent() == null); - assertTrue("isRoot", t.isRoot()); - assertTrue("no name", t.getName() == null); - assertTrue("no nameUTF8", t.getNameUTF8() == null); - assertTrue("has entries array", t.members() != null); - assertEquals("entries is empty", 0, t.members().length); - assertEquals("full name is empty", "", t.getFullName()); - assertTrue("no id", t.getId() == null); - assertTrue("database is r", t.getRepository() == db); - assertTrue("no foo child", t.findTreeMember("foo") == null); - assertTrue("no foo child", t.findBlobMember("foo") == null); - } - - @Test - public void test002_addFile() throws IOException { - final Tree t = new Tree(db); - t.setId(SOME_FAKE_ID); - assertTrue("has id", t.getId() != null); - assertFalse("not modified", t.isModified()); - - final String n = "bob"; - final FileTreeEntry f = t.addFile(n); - assertNotNull("have file", f); - assertEquals("name matches", n, f.getName()); - assertEquals("name matches", f.getName(), new String(f.getNameUTF8(), - "UTF-8")); - assertEquals("full name matches", n, f.getFullName()); - assertTrue("no id", f.getId() == null); - assertTrue("is modified", t.isModified()); - assertTrue("has no id", t.getId() == null); - assertTrue("found bob", t.findBlobMember(f.getName()) == f); - - final TreeEntry[] i = t.members(); - assertNotNull("members array not null", i); - assertTrue("iterator is not empty", i != null && i.length > 0); - assertTrue("iterator returns file", i != null && i[0] == f); - assertTrue("iterator is empty", i != null && i.length == 1); - } - - @Test - public void test004_addTree() throws IOException { - final Tree t = new Tree(db); - t.setId(SOME_FAKE_ID); - assertTrue("has id", t.getId() != null); - assertFalse("not modified", t.isModified()); - - final String n = "bob"; - final Tree f = t.addTree(n); - assertNotNull("have tree", f); - assertEquals("name matches", n, f.getName()); - assertEquals("name matches", f.getName(), new String(f.getNameUTF8(), - "UTF-8")); - assertEquals("full name matches", n, f.getFullName()); - assertTrue("no id", f.getId() == null); - assertTrue("parent matches", f.getParent() == t); - assertTrue("repository matches", f.getRepository() == db); - assertTrue("isLoaded", f.isLoaded()); - assertFalse("has items", f.members().length > 0); - assertFalse("is root", f.isRoot()); - assertTrue("parent is modified", t.isModified()); - assertTrue("parent has no id", t.getId() == null); - assertTrue("found bob child", t.findTreeMember(f.getName()) == f); - - final TreeEntry[] i = t.members(); - assertTrue("iterator is not empty", i.length > 0); - assertTrue("iterator returns file", i[0] == f); - assertEquals("iterator is empty", 1, i.length); - } - - @Test - public void test005_addRecursiveFile() throws IOException { - final Tree t = new Tree(db); - final FileTreeEntry f = t.addFile("a/b/c"); - assertNotNull("created f", f); - assertEquals("c", f.getName()); - assertEquals("b", f.getParent().getName()); - assertEquals("a", f.getParent().getParent().getName()); - assertTrue("t is great-grandparent", t == f.getParent().getParent() - .getParent()); - } - - @Test - public void test005_addRecursiveTree() throws IOException { - final Tree t = new Tree(db); - final Tree f = t.addTree("a/b/c"); - assertNotNull("created f", f); - assertEquals("c", f.getName()); - assertEquals("b", f.getParent().getName()); - assertEquals("a", f.getParent().getParent().getName()); - assertTrue("t is great-grandparent", t == f.getParent().getParent() - .getParent()); - } - - @Test - public void test006_addDeepTree() throws IOException { - final Tree t = new Tree(db); - - final Tree e = t.addTree("e"); - assertNotNull("have e", e); - assertTrue("e.parent == t", e.getParent() == t); - final Tree f = t.addTree("f"); - assertNotNull("have f", f); - assertTrue("f.parent == t", f.getParent() == t); - final Tree g = f.addTree("g"); - assertNotNull("have g", g); - assertTrue("g.parent == f", g.getParent() == f); - final Tree h = g.addTree("h"); - assertNotNull("have h", h); - assertTrue("h.parent = g", h.getParent() == g); - - h.setId(SOME_FAKE_ID); - assertTrue("h not modified", !h.isModified()); - g.setId(SOME_FAKE_ID); - assertTrue("g not modified", !g.isModified()); - f.setId(SOME_FAKE_ID); - assertTrue("f not modified", !f.isModified()); - e.setId(SOME_FAKE_ID); - assertTrue("e not modified", !e.isModified()); - t.setId(SOME_FAKE_ID); - assertTrue("t not modified.", !t.isModified()); - - assertEquals("full path of h ok", "f/g/h", h.getFullName()); - assertTrue("Can find h", t.findTreeMember(h.getFullName()) == h); - assertTrue("Can't find f/z", t.findBlobMember("f/z") == null); - assertTrue("Can't find y/z", t.findBlobMember("y/z") == null); - - final FileTreeEntry i = h.addFile("i"); - assertNotNull(i); - assertEquals("full path of i ok", "f/g/h/i", i.getFullName()); - assertTrue("Can find i", t.findBlobMember(i.getFullName()) == i); - assertTrue("h modified", h.isModified()); - assertTrue("g modified", g.isModified()); - assertTrue("f modified", f.isModified()); - assertTrue("e not modified", !e.isModified()); - assertTrue("t modified", t.isModified()); - - assertTrue("h no id", h.getId() == null); - assertTrue("g no id", g.getId() == null); - assertTrue("f no id", f.getId() == null); - assertTrue("e has id", e.getId() != null); - assertTrue("t no id", t.getId() == null); - } - - @Test - public void test007_manyFileLookup() throws IOException { - final Tree t = new Tree(db); - final List files = new ArrayList(26 * 26); - for (char level1 = 'a'; level1 <= 'z'; level1++) { - for (char level2 = 'a'; level2 <= 'z'; level2++) { - final String n = "." + level1 + level2 + "9"; - final FileTreeEntry f = t.addFile(n); - assertNotNull("File " + n + " added.", f); - assertEquals(n, f.getName()); - files.add(f); - } - } - assertEquals(files.size(), t.memberCount()); - final TreeEntry[] ents = t.members(); - assertNotNull(ents); - assertEquals(files.size(), ents.length); - for (int k = 0; k < ents.length; k++) { - assertTrue("File " + files.get(k).getName() - + " is at " + k + ".", files.get(k) == ents[k]); - } - } - - @Test - public void test008_SubtreeInternalSorting() throws IOException { - final Tree t = new Tree(db); - final FileTreeEntry e0 = t.addFile("a-b"); - final FileTreeEntry e1 = t.addFile("a-"); - final FileTreeEntry e2 = t.addFile("a=b"); - final Tree e3 = t.addTree("a"); - final FileTreeEntry e4 = t.addFile("a="); - - final TreeEntry[] ents = t.members(); - assertSame(e1, ents[0]); - assertSame(e0, ents[1]); - assertSame(e3, ents[2]); - assertSame(e4, ents[3]); - assertSame(e2, ents[4]); - } - - @Test - public void test009_SymlinkAndGitlink() throws IOException { - final Tree symlinkTree = mapTree("symlink"); - assertTrue("Symlink entry exists", symlinkTree.existsBlob("symlink.txt")); - final Tree gitlinkTree = mapTree("gitlink"); - assertTrue("Gitlink entry exists", gitlinkTree.existsBlob("submodule")); - } - - private Tree mapTree(String name) throws IOException { - ObjectId id = db.resolve(name + "^{tree}"); - return new Tree(db, id, db.open(id).getCachedBytes()); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java index 2a59f58c6..9c9edc147 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java @@ -47,11 +47,10 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileTreeEntry; +import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.lib.TreeFormatter; import org.junit.Test; @SuppressWarnings("deprecation") @@ -220,28 +219,24 @@ public void testEmptyTreeCorruption() throws Exception { .fromString("abbbfafe3129f85747aba7bfac992af77134c607"); final RevTree tree_root, tree_A, tree_AB; final RevCommit b; - { - Tree root = new Tree(db); - Tree A = root.addTree("A"); - FileTreeEntry B = root.addFile("B"); - B.setId(bId); + try (ObjectInserter inserter = db.newObjectInserter()) { + ObjectId empty = inserter.insert(new TreeFormatter()); - Tree A_A = A.addTree("A"); - Tree A_B = A.addTree("B"); + TreeFormatter A = new TreeFormatter(); + A.append("A", FileMode.TREE, empty); + A.append("B", FileMode.TREE, empty); + ObjectId idA = inserter.insert(A); - try (final ObjectInserter inserter = db.newObjectInserter()) { - A_A.setId(inserter.insert(Constants.OBJ_TREE, A_A.format())); - A_B.setId(inserter.insert(Constants.OBJ_TREE, A_B.format())); - A.setId(inserter.insert(Constants.OBJ_TREE, A.format())); - root.setId(inserter.insert(Constants.OBJ_TREE, root.format())); - inserter.flush(); - } + TreeFormatter root = new TreeFormatter(); + root.append("A", FileMode.TREE, idA); + root.append("B", FileMode.REGULAR_FILE, bId); + ObjectId idRoot = inserter.insert(root); + inserter.flush(); - tree_root = rw.parseTree(root.getId()); - tree_A = rw.parseTree(A.getId()); - tree_AB = rw.parseTree(A_A.getId()); - assertSame(tree_AB, rw.parseTree(A_B.getId())); - b = commit(rw.parseTree(root.getId())); + tree_root = objw.parseTree(idRoot); + tree_A = objw.parseTree(idA); + tree_AB = objw.parseTree(empty); + b = commit(tree_root); } markStart(b); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java index aca7c80fd..c3ff7df8f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java @@ -44,7 +44,6 @@ package org.eclipse.jgit.treewalk; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; -import static org.eclipse.jgit.lib.Constants.OBJ_TREE; import static org.eclipse.jgit.lib.Constants.encode; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -54,11 +53,10 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.lib.TreeFormatter; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.junit.Test; -@SuppressWarnings("deprecation") public class TreeWalkBasicDiffTest extends RepositoryTestCase { @Test public void testMissingSubtree_DetectFileAdded_FileModified() @@ -72,62 +70,63 @@ public void testMissingSubtree_DetectFileAdded_FileModified() // Create sub-a/empty, sub-c/empty = hello. { - final Tree root = new Tree(db); + TreeFormatter root = new TreeFormatter(); { - final Tree subA = root.addTree("sub-a"); - subA.addFile("empty").setId(aFileId); - subA.setId(inserter.insert(OBJ_TREE, subA.format())); + TreeFormatter subA = new TreeFormatter(); + subA.append("empty", FileMode.REGULAR_FILE, aFileId); + root.append("sub-a", FileMode.TREE, inserter.insert(subA)); } { - final Tree subC = root.addTree("sub-c"); - subC.addFile("empty").setId(cFileId1); - subC.setId(inserter.insert(OBJ_TREE, subC.format())); + TreeFormatter subC = new TreeFormatter(); + subC.append("empty", FileMode.REGULAR_FILE, cFileId1); + root.append("sub-c", FileMode.TREE, inserter.insert(subC)); } - oldTree = inserter.insert(OBJ_TREE, root.format()); + oldTree = inserter.insert(root); } // Create sub-a/empty, sub-b/empty, sub-c/empty. { - final Tree root = new Tree(db); + TreeFormatter root = new TreeFormatter(); { - final Tree subA = root.addTree("sub-a"); - subA.addFile("empty").setId(aFileId); - subA.setId(inserter.insert(OBJ_TREE, subA.format())); + TreeFormatter subA = new TreeFormatter(); + subA.append("empty", FileMode.REGULAR_FILE, aFileId); + root.append("sub-a", FileMode.TREE, inserter.insert(subA)); } { - final Tree subB = root.addTree("sub-b"); - subB.addFile("empty").setId(bFileId); - subB.setId(inserter.insert(OBJ_TREE, subB.format())); + TreeFormatter subB = new TreeFormatter(); + subB.append("empty", FileMode.REGULAR_FILE, bFileId); + root.append("sub-b", FileMode.TREE, inserter.insert(subB)); } { - final Tree subC = root.addTree("sub-c"); - subC.addFile("empty").setId(cFileId2); - subC.setId(inserter.insert(OBJ_TREE, subC.format())); + TreeFormatter subC = new TreeFormatter(); + subC.append("empty", FileMode.REGULAR_FILE, cFileId2); + root.append("sub-c", FileMode.TREE, inserter.insert(subC)); } - newTree = inserter.insert(OBJ_TREE, root.format()); + newTree = inserter.insert(root); } inserter.flush(); } - final TreeWalk tw = new TreeWalk(db); - tw.reset(oldTree, newTree); - tw.setRecursive(true); - tw.setFilter(TreeFilter.ANY_DIFF); + try (TreeWalk tw = new TreeWalk(db)) { + tw.reset(oldTree, newTree); + tw.setRecursive(true); + tw.setFilter(TreeFilter.ANY_DIFF); - assertTrue(tw.next()); - assertEquals("sub-b/empty", tw.getPathString()); - assertEquals(FileMode.MISSING, tw.getFileMode(0)); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); - assertEquals(ObjectId.zeroId(), tw.getObjectId(0)); - assertEquals(bFileId, tw.getObjectId(1)); + assertTrue(tw.next()); + assertEquals("sub-b/empty", tw.getPathString()); + assertEquals(FileMode.MISSING, tw.getFileMode(0)); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); + assertEquals(ObjectId.zeroId(), tw.getObjectId(0)); + assertEquals(bFileId, tw.getObjectId(1)); - assertTrue(tw.next()); - assertEquals("sub-c/empty", tw.getPathString()); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0)); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); - assertEquals(cFileId1, tw.getObjectId(0)); - assertEquals(cFileId2, tw.getObjectId(1)); + assertTrue(tw.next()); + assertEquals("sub-c/empty", tw.getPathString()); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0)); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); + assertEquals(cFileId1, tw.getObjectId(0)); + assertEquals(cFileId2, tw.getObjectId(1)); - assertFalse(tw.next()); + assertFalse(tw.next()); + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java deleted file mode 100644 index 6811417ee..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.IOException; - -/** - * A representation of a file (blob) object in a {@link Tree}. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - */ -@Deprecated -public class FileTreeEntry extends TreeEntry { - private FileMode mode; - - /** - * Constructor for a File (blob) object. - * - * @param parent - * The {@link Tree} holding this object (or null) - * @param id - * the SHA-1 of the blob (or null for a yet unhashed file) - * @param nameUTF8 - * raw object name in the parent tree - * @param execute - * true if the executable flag is set - */ - public FileTreeEntry(final Tree parent, final ObjectId id, - final byte[] nameUTF8, final boolean execute) { - super(parent, id, nameUTF8); - setExecutable(execute); - } - - public FileMode getMode() { - return mode; - } - - /** - * @return true if this file is executable - */ - public boolean isExecutable() { - return getMode().equals(FileMode.EXECUTABLE_FILE); - } - - /** - * @param execute set/reset the executable flag - */ - public void setExecutable(final boolean execute) { - mode = execute ? FileMode.EXECUTABLE_FILE : FileMode.REGULAR_FILE; - } - - /** - * @return an {@link ObjectLoader} that will return the data - * @throws IOException - */ - public ObjectLoader openReader() throws IOException { - return getRepository().open(getId(), Constants.OBJ_BLOB); - } - - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(' '); - r.append(isExecutable() ? 'X' : 'F'); - r.append(' '); - r.append(getFullName()); - return r.toString(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java deleted file mode 100644 index 936fd82bf..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2009, Jonas Fonseca - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -/** - * A tree entry representing a gitlink entry used for submodules. - * - * Note. Java cannot really handle these as file system objects. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - */ -@Deprecated -public class GitlinkTreeEntry extends TreeEntry { - - /** - * Construct a {@link GitlinkTreeEntry} with the specified name and SHA-1 in - * the specified parent - * - * @param parent - * @param id - * @param nameUTF8 - */ - public GitlinkTreeEntry(final Tree parent, final ObjectId id, - final byte[] nameUTF8) { - super(parent, id, nameUTF8); - } - - public FileMode getMode() { - return FileMode.GITLINK; - } - - @Override - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(" G "); //$NON-NLS-1$ - r.append(getFullName()); - return r.toString(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java deleted file mode 100644 index c7e41bce0..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -/** - * A tree entry representing a symbolic link. - * - * Note. Java cannot really handle these as file system objects. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - */ -@Deprecated -public class SymlinkTreeEntry extends TreeEntry { - - /** - * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in - * the specified parent - * - * @param parent - * @param id - * @param nameUTF8 - */ - public SymlinkTreeEntry(final Tree parent, final ObjectId id, - final byte[] nameUTF8) { - super(parent, id, nameUTF8); - } - - public FileMode getMode() { - return FileMode.SYMLINK; - } - - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(" S "); //$NON-NLS-1$ - r.append(getFullName()); - return r.toString(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java deleted file mode 100644 index 43bd489dc..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java +++ /dev/null @@ -1,601 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2007-2008, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.IOException; -import java.text.MessageFormat; - -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.errors.EntryExistsException; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.errors.ObjectWritingException; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * A representation of a Git tree entry. A Tree is a directory in Git. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - */ -@Deprecated -public class Tree extends TreeEntry { - private static final TreeEntry[] EMPTY_TREE = {}; - - /** - * Compare two names represented as bytes. Since git treats names of trees and - * blobs differently we have one parameter that represents a '/' for trees. For - * other objects the value should be NUL. The names are compare by their positive - * byte value (0..255). - * - * A blob and a tree with the same name will not compare equal. - * - * @param a name - * @param b name - * @param lasta '/' if a is a tree, else NUL - * @param lastb '/' if b is a tree, else NUL - * - * @return < 0 if a is sorted before b, 0 if they are the same, else b - */ - public static final int compareNames(final byte[] a, final byte[] b, final int lasta,final int lastb) { - return compareNames(a, b, 0, b.length, lasta, lastb); - } - - private static final int compareNames(final byte[] a, final byte[] nameUTF8, - final int nameStart, final int nameEnd, final int lasta, int lastb) { - int j,k; - for (j = 0, k = nameStart; j < a.length && k < nameEnd; j++, k++) { - final int aj = a[j] & 0xff; - final int bk = nameUTF8[k] & 0xff; - if (aj < bk) - return -1; - else if (aj > bk) - return 1; - } - if (j < a.length) { - int aj = a[j]&0xff; - if (aj < lastb) - return -1; - else if (aj > lastb) - return 1; - else - if (j == a.length - 1) - return 0; - else - return -1; - } - if (k < nameEnd) { - int bk = nameUTF8[k] & 0xff; - if (lasta < bk) - return -1; - else if (lasta > bk) - return 1; - else - if (k == nameEnd - 1) - return 0; - else - return 1; - } - if (lasta < lastb) - return -1; - else if (lasta > lastb) - return 1; - - final int namelength = nameEnd - nameStart; - if (a.length == namelength) - return 0; - else if (a.length < namelength) - return -1; - else - return 1; - } - - private static final byte[] substring(final byte[] s, final int nameStart, - final int nameEnd) { - if (nameStart == 0 && nameStart == s.length) - return s; - final byte[] n = new byte[nameEnd - nameStart]; - System.arraycopy(s, nameStart, n, 0, n.length); - return n; - } - - private static final int binarySearch(final TreeEntry[] entries, - final byte[] nameUTF8, final int nameUTF8last, final int nameStart, final int nameEnd) { - if (entries.length == 0) - return -1; - int high = entries.length; - int low = 0; - do { - final int mid = (low + high) >>> 1; - final int cmp = compareNames(entries[mid].getNameUTF8(), nameUTF8, - nameStart, nameEnd, TreeEntry.lastChar(entries[mid]), nameUTF8last); - if (cmp < 0) - low = mid + 1; - else if (cmp == 0) - return mid; - else - high = mid; - } while (low < high); - return -(low + 1); - } - - private final Repository db; - - private TreeEntry[] contents; - - /** - * Constructor for a new Tree - * - * @param repo The repository that owns the Tree. - */ - public Tree(final Repository repo) { - super(null, null, null); - db = repo; - contents = EMPTY_TREE; - } - - /** - * Construct a Tree object with known content and hash value - * - * @param repo - * @param myId - * @param raw - * @throws IOException - */ - public Tree(final Repository repo, final ObjectId myId, final byte[] raw) - throws IOException { - super(null, myId, null); - db = repo; - readTree(raw); - } - - /** - * Construct a new Tree under another Tree - * - * @param parent - * @param nameUTF8 - */ - public Tree(final Tree parent, final byte[] nameUTF8) { - super(parent, null, nameUTF8); - db = parent.getRepository(); - contents = EMPTY_TREE; - } - - /** - * Construct a Tree with a known SHA-1 under another tree. Data is not yet - * specified and will have to be loaded on demand. - * - * @param parent - * @param id - * @param nameUTF8 - */ - public Tree(final Tree parent, final ObjectId id, final byte[] nameUTF8) { - super(parent, id, nameUTF8); - db = parent.getRepository(); - } - - public FileMode getMode() { - return FileMode.TREE; - } - - /** - * @return true if this Tree is the top level Tree. - */ - public boolean isRoot() { - return getParent() == null; - } - - public Repository getRepository() { - return db; - } - - /** - * @return true of the data of this Tree is loaded - */ - public boolean isLoaded() { - return contents != null; - } - - /** - * Forget the in-memory data for this tree. - */ - public void unload() { - if (isModified()) - throw new IllegalStateException(JGitText.get().cannotUnloadAModifiedTree); - contents = null; - } - - /** - * Adds a new or existing file with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param name Name - * @return a {@link FileTreeEntry} for the added file. - * @throws IOException - */ - public FileTreeEntry addFile(final String name) throws IOException { - return addFile(Repository.gitInternalSlash(Constants.encode(name)), 0); - } - - /** - * Adds a new or existing file with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param s an array containing the name - * @param offset when the name starts in the tree. - * - * @return a {@link FileTreeEntry} for the added file. - * @throws IOException - */ - public FileTreeEntry addFile(final byte[] s, final int offset) - throws IOException { - int slash; - int p; - - for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { - // search for path component terminator - } - - ensureLoaded(); - byte xlast = slash= 0 && slash < s.length && contents[p] instanceof Tree) - return ((Tree) contents[p]).addFile(s, slash + 1); - - final byte[] newName = substring(s, offset, slash); - if (p >= 0) - throw new EntryExistsException(RawParseUtils.decode(newName)); - else if (slash < s.length) { - final Tree t = new Tree(this, newName); - insertEntry(p, t); - return t.addFile(s, slash + 1); - } else { - final FileTreeEntry f = new FileTreeEntry(this, null, newName, - false); - insertEntry(p, f); - return f; - } - } - - /** - * Adds a new or existing Tree with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param name Name - * @return a {@link FileTreeEntry} for the added tree. - * @throws IOException - */ - public Tree addTree(final String name) throws IOException { - return addTree(Repository.gitInternalSlash(Constants.encode(name)), 0); - } - - /** - * Adds a new or existing Tree with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param s an array containing the name - * @param offset when the name starts in the tree. - * - * @return a {@link FileTreeEntry} for the added tree. - * @throws IOException - */ - public Tree addTree(final byte[] s, final int offset) throws IOException { - int slash; - int p; - - for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { - // search for path component terminator - } - - ensureLoaded(); - p = binarySearch(contents, s, (byte)'/', offset, slash); - if (p >= 0 && slash < s.length && contents[p] instanceof Tree) - return ((Tree) contents[p]).addTree(s, slash + 1); - - final byte[] newName = substring(s, offset, slash); - if (p >= 0) - throw new EntryExistsException(RawParseUtils.decode(newName)); - - final Tree t = new Tree(this, newName); - insertEntry(p, t); - return slash == s.length ? t : t.addTree(s, slash + 1); - } - - /** - * Add the specified tree entry to this tree. - * - * @param e - * @throws IOException - */ - public void addEntry(final TreeEntry e) throws IOException { - final int p; - - ensureLoaded(); - p = binarySearch(contents, e.getNameUTF8(), TreeEntry.lastChar(e), 0, e.getNameUTF8().length); - if (p < 0) { - e.attachParent(this); - insertEntry(p, e); - } else { - throw new EntryExistsException(e.getName()); - } - } - - private void insertEntry(int p, final TreeEntry e) { - final TreeEntry[] c = contents; - final TreeEntry[] n = new TreeEntry[c.length + 1]; - p = -(p + 1); - for (int k = c.length - 1; k >= p; k--) - n[k + 1] = c[k]; - n[p] = e; - for (int k = p - 1; k >= 0; k--) - n[k] = c[k]; - contents = n; - setModified(); - } - - void removeEntry(final TreeEntry e) { - final TreeEntry[] c = contents; - final int p = binarySearch(c, e.getNameUTF8(), TreeEntry.lastChar(e), 0, - e.getNameUTF8().length); - if (p >= 0) { - final TreeEntry[] n = new TreeEntry[c.length - 1]; - for (int k = c.length - 1; k > p; k--) - n[k - 1] = c[k]; - for (int k = p - 1; k >= 0; k--) - n[k] = c[k]; - contents = n; - setModified(); - } - } - - /** - * @return number of members in this tree - * @throws IOException - */ - public int memberCount() throws IOException { - ensureLoaded(); - return contents.length; - } - - /** - * Return all members of the tree sorted in Git order. - * - * Entries are sorted by the numerical unsigned byte - * values with (sub)trees having an implicit '/'. An - * example of a tree with three entries. a:b is an - * actual file name here. - * - *

- * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a.b - * 040000 tree 4277b6e69d25e5efa77c455340557b384a4c018a a - * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a:b - * - * @return all entries in this Tree, sorted. - * @throws IOException - */ - public TreeEntry[] members() throws IOException { - ensureLoaded(); - final TreeEntry[] c = contents; - if (c.length != 0) { - final TreeEntry[] r = new TreeEntry[c.length]; - for (int k = c.length - 1; k >= 0; k--) - r[k] = c[k]; - return r; - } else - return c; - } - - private boolean exists(final String s, byte slast) throws IOException { - return findMember(s, slast) != null; - } - - /** - * @param path to the tree. - * @return true if a tree with the specified path can be found under this - * tree. - * @throws IOException - */ - public boolean existsTree(String path) throws IOException { - return exists(path,(byte)'/'); - } - - /** - * @param path of the non-tree entry. - * @return true if a blob, symlink, or gitlink with the specified name - * can be found under this tree. - * @throws IOException - */ - public boolean existsBlob(String path) throws IOException { - return exists(path,(byte)0); - } - - private TreeEntry findMember(final String s, byte slast) throws IOException { - return findMember(Repository.gitInternalSlash(Constants.encode(s)), slast, 0); - } - - private TreeEntry findMember(final byte[] s, final byte slast, final int offset) - throws IOException { - int slash; - int p; - - for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { - // search for path component terminator - } - - ensureLoaded(); - byte xlast = slash= 0) { - final TreeEntry r = contents[p]; - if (slash < s.length-1) - return r instanceof Tree ? ((Tree) r).findMember(s, slast, slash + 1) - : null; - return r; - } - return null; - } - - /** - * @param s - * blob name - * @return a {@link TreeEntry} representing an object with the specified - * relative path. - * @throws IOException - */ - public TreeEntry findBlobMember(String s) throws IOException { - return findMember(s,(byte)0); - } - - /** - * @param s Tree Name - * @return a Tree with the name s or null - * @throws IOException - */ - public TreeEntry findTreeMember(String s) throws IOException { - return findMember(s,(byte)'/'); - } - - private void ensureLoaded() throws IOException, MissingObjectException { - if (!isLoaded()) { - ObjectLoader ldr = db.open(getId(), Constants.OBJ_TREE); - readTree(ldr.getCachedBytes()); - } - } - - private void readTree(final byte[] raw) throws IOException { - final int rawSize = raw.length; - int rawPtr = 0; - TreeEntry[] temp; - int nextIndex = 0; - - while (rawPtr < rawSize) { - while (rawPtr < rawSize && raw[rawPtr] != 0) - rawPtr++; - rawPtr++; - rawPtr += Constants.OBJECT_ID_LENGTH; - nextIndex++; - } - - temp = new TreeEntry[nextIndex]; - rawPtr = 0; - nextIndex = 0; - while (rawPtr < rawSize) { - int c = raw[rawPtr++]; - if (c < '0' || c > '7') - throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidEntryMode); - int mode = c - '0'; - for (;;) { - c = raw[rawPtr++]; - if (' ' == c) - break; - else if (c < '0' || c > '7') - throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidMode); - mode <<= 3; - mode += c - '0'; - } - - int nameLen = 0; - while (raw[rawPtr + nameLen] != 0) - nameLen++; - final byte[] name = new byte[nameLen]; - System.arraycopy(raw, rawPtr, name, 0, nameLen); - rawPtr += nameLen + 1; - - final ObjectId id = ObjectId.fromRaw(raw, rawPtr); - rawPtr += Constants.OBJECT_ID_LENGTH; - - final TreeEntry ent; - if (FileMode.REGULAR_FILE.equals(mode)) - ent = new FileTreeEntry(this, id, name, false); - else if (FileMode.EXECUTABLE_FILE.equals(mode)) - ent = new FileTreeEntry(this, id, name, true); - else if (FileMode.TREE.equals(mode)) - ent = new Tree(this, id, name); - else if (FileMode.SYMLINK.equals(mode)) - ent = new SymlinkTreeEntry(this, id, name); - else if (FileMode.GITLINK.equals(mode)) - ent = new GitlinkTreeEntry(this, id, name); - else - throw new CorruptObjectException(getId(), MessageFormat.format( - JGitText.get().corruptObjectInvalidMode2, Integer.toOctalString(mode))); - temp[nextIndex++] = ent; - } - - contents = temp; - } - - /** - * Format this Tree in canonical format. - * - * @return canonical encoding of the tree object. - * @throws IOException - * the tree cannot be loaded, or its not in a writable state. - */ - public byte[] format() throws IOException { - TreeFormatter fmt = new TreeFormatter(); - for (TreeEntry e : members()) { - ObjectId id = e.getId(); - if (id == null) - throw new ObjectWritingException(MessageFormat.format(JGitText - .get().objectAtPathDoesNotHaveId, e.getFullName())); - - fmt.append(e.getNameUTF8(), e.getMode(), id); - } - return fmt.toByteArray(); - } - - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(" T "); //$NON-NLS-1$ - r.append(getFullName()); - return r.toString(); - } - -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java deleted file mode 100644 index a1ffa6805..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2007-2008, Robin Rosenberg - * Copyright (C) 2006-2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.IOException; - -import org.eclipse.jgit.util.RawParseUtils; - -/** - * This class represents an entry in a tree, like a blob or another tree. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - */ -@Deprecated -public abstract class TreeEntry implements Comparable { - private byte[] nameUTF8; - - private Tree parent; - - private ObjectId id; - - /** - * Construct a named tree entry. - * - * @param myParent - * @param myId - * @param myNameUTF8 - */ - protected TreeEntry(final Tree myParent, final ObjectId myId, - final byte[] myNameUTF8) { - nameUTF8 = myNameUTF8; - parent = myParent; - id = myId; - } - - /** - * @return parent of this tree. - */ - public Tree getParent() { - return parent; - } - - /** - * Delete this entry. - */ - public void delete() { - getParent().removeEntry(this); - detachParent(); - } - - /** - * Detach this entry from it's parent. - */ - public void detachParent() { - parent = null; - } - - void attachParent(final Tree p) { - parent = p; - } - - /** - * @return the repository owning this entry. - */ - public Repository getRepository() { - return getParent().getRepository(); - } - - /** - * @return the raw byte name of this entry. - */ - public byte[] getNameUTF8() { - return nameUTF8; - } - - /** - * @return the name of this entry. - */ - public String getName() { - if (nameUTF8 != null) - return RawParseUtils.decode(nameUTF8); - return null; - } - - /** - * Rename this entry. - * - * @param n The new name - * @throws IOException - */ - public void rename(final String n) throws IOException { - rename(Constants.encode(n)); - } - - /** - * Rename this entry. - * - * @param n The new name - * @throws IOException - */ - public void rename(final byte[] n) throws IOException { - final Tree t = getParent(); - if (t != null) { - delete(); - } - nameUTF8 = n; - if (t != null) { - t.addEntry(this); - } - } - - /** - * @return true if this entry is new or modified since being loaded. - */ - public boolean isModified() { - return getId() == null; - } - - /** - * Mark this entry as modified. - */ - public void setModified() { - setId(null); - } - - /** - * @return SHA-1 of this tree entry (null for new unhashed entries) - */ - public ObjectId getId() { - return id; - } - - /** - * Set (update) the SHA-1 of this entry. Invalidates the id's of all - * entries above this entry as they will have to be recomputed. - * - * @param n SHA-1 for this entry. - */ - public void setId(final ObjectId n) { - // If we have a parent and our id is being cleared or changed then force - // the parent's id to become unset as it depends on our id. - // - final Tree p = getParent(); - if (p != null && id != n) { - if ((id == null && n != null) || (id != null && n == null) - || !id.equals(n)) { - p.setId(null); - } - } - - id = n; - } - - /** - * @return repository relative name of this entry - */ - public String getFullName() { - final StringBuilder r = new StringBuilder(); - appendFullName(r); - return r.toString(); - } - - /** - * @return repository relative name of the entry - * FIXME better encoding - */ - public byte[] getFullNameUTF8() { - return getFullName().getBytes(); - } - - public int compareTo(final Object o) { - if (this == o) - return 0; - if (o instanceof TreeEntry) - return Tree.compareNames(nameUTF8, ((TreeEntry) o).nameUTF8, lastChar(this), lastChar((TreeEntry)o)); - return -1; - } - - /** - * Helper for accessing tree/blob methods. - * - * @param treeEntry - * @return '/' for Tree entries and NUL for non-treeish objects. - */ - final public static int lastChar(TreeEntry treeEntry) { - if (!(treeEntry instanceof Tree)) - return '\0'; - else - return '/'; - } - - /** - * @return mode (type of object) - */ - public abstract FileMode getMode(); - - private void appendFullName(final StringBuilder r) { - final TreeEntry p = getParent(); - final String n = getName(); - if (p != null) { - p.appendFullName(r); - if (r.length() > 0) { - r.append('/'); - } - } - if (n != null) { - r.append(n); - } - } -} From 31d92ace5b0b4ca66b3fa191b8a1207d6e8fecc6 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Mon, 11 Jan 2016 12:30:35 -0800 Subject: [PATCH 72/89] RevCommit: Better support invalid encoding headers With this support we no longer need the 'utf-8' alias. UTF-8 will be automatically tried when the encoding header is not recognized and used if the character sequence cleanly decodes as UTF-8. Modernize some of the references to use StandardCharsets. Change-Id: I4c0c88750475560e1f2263180c4a98eb8febeca0 --- .../jgit/revwalk/RevCommitParseTest.java | 85 +++++++++++++++++ .../eclipse/jgit/revwalk/RevTagParseTest.java | 39 ++++++++ .../org/eclipse/jgit/revwalk/RevCommit.java | 65 ++++++++++--- .../src/org/eclipse/jgit/revwalk/RevTag.java | 39 +++++--- .../org/eclipse/jgit/util/RawParseUtils.java | 91 +++++++++++++------ 5 files changed, 263 insertions(+), 56 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java index beda2a7b9..885c1b5b2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java @@ -43,13 +43,18 @@ package org.eclipse.jgit.revwalk; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; import java.util.TimeZone; import org.eclipse.jgit.junit.RepositoryTestCase; @@ -303,6 +308,86 @@ public void testParse_explicit_bad_encoded2() throws Exception { assertEquals("\u304d\u308c\u3044\n\nHi\n", c.getFullMessage()); } + @Test + public void testParse_incorrectUtf8Name() throws Exception { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n" + .getBytes(UTF_8)); + b.write("author au 1218123387 +0700\n".getBytes(UTF_8)); + b.write("committer co 1218123390 -0500\n" + .getBytes(UTF_8)); + b.write("encoding 'utf8'\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("Sm\u00f6rg\u00e5sbord\n".getBytes(UTF_8)); + + RevCommit c = new RevCommit( + id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toByteArray()); + assertEquals("'utf8'", c.getEncodingName()); + assertEquals("Sm\u00f6rg\u00e5sbord\n", c.getFullMessage()); + + try { + c.getEncoding(); + fail("Expected " + IllegalCharsetNameException.class); + } catch (IllegalCharsetNameException badName) { + assertEquals("'utf8'", badName.getMessage()); + } + } + + @Test + public void testParse_illegalEncoding() throws Exception { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("author au 1218123387 +0700\n".getBytes(UTF_8)); + b.write("committer co 1218123390 -0500\n".getBytes(UTF_8)); + b.write("encoding utf-8logoutputencoding=gbk\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("message\n".getBytes(UTF_8)); + + RevCommit c = new RevCommit( + id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toByteArray()); + assertEquals("utf-8logoutputencoding=gbk", c.getEncodingName()); + assertEquals("message\n", c.getFullMessage()); + assertEquals("message", c.getShortMessage()); + assertTrue(c.getFooterLines().isEmpty()); + assertEquals("au", c.getAuthorIdent().getName()); + + try { + c.getEncoding(); + fail("Expected " + IllegalCharsetNameException.class); + } catch (IllegalCharsetNameException badName) { + assertEquals("utf-8logoutputencoding=gbk", badName.getMessage()); + } + } + + @Test + public void testParse_unsupportedEncoding() throws Exception { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("author au 1218123387 +0700\n".getBytes(UTF_8)); + b.write("committer co 1218123390 -0500\n".getBytes(UTF_8)); + b.write("encoding it_IT.UTF8\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("message\n".getBytes(UTF_8)); + + RevCommit c = new RevCommit( + id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toByteArray()); + assertEquals("it_IT.UTF8", c.getEncodingName()); + assertEquals("message\n", c.getFullMessage()); + assertEquals("message", c.getShortMessage()); + assertTrue(c.getFooterLines().isEmpty()); + assertEquals("au", c.getAuthorIdent().getName()); + + try { + c.getEncoding(); + fail("Expected " + UnsupportedCharsetException.class); + } catch (UnsupportedCharsetException badName) { + assertEquals("it_IT.UTF8", badName.getMessage()); + } + } + @Test public void testParse_NoMessage() throws Exception { final String msg = ""; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java index 614f49bf0..82505caf2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.revwalk; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -361,6 +362,44 @@ public void testParse_explicit_bad_encoded2() throws Exception { assertEquals("\u304d\u308c\u3044\n\nHi\n", c.getFullMessage()); } + @Test + public void testParse_illegalEncoding() throws Exception { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("type tree\n".getBytes(UTF_8)); + b.write("tag v1.0\n".getBytes(UTF_8)); + b.write("tagger t 1218123387 +0700\n".getBytes(UTF_8)); + b.write("encoding utf-8logoutputencoding=gbk\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("message\n".getBytes(UTF_8)); + + RevTag t = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + t.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("t", t.getTaggerIdent().getName()); + assertEquals("message", t.getShortMessage()); + assertEquals("message\n", t.getFullMessage()); + } + + @Test + public void testParse_unsupportedEncoding() throws Exception { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes(UTF_8)); + b.write("type tree\n".getBytes(UTF_8)); + b.write("tag v1.0\n".getBytes(UTF_8)); + b.write("tagger t 1218123387 +0700\n".getBytes(UTF_8)); + b.write("encoding it_IT.UTF8\n".getBytes(UTF_8)); + b.write("\n".getBytes(UTF_8)); + b.write("message\n".getBytes(UTF_8)); + + RevTag t = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + t.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("t", t.getTaggerIdent().getName()); + assertEquals("message", t.getShortMessage()); + assertEquals("message\n", t.getFullMessage()); + } + @Test public void testParse_NoMessage() throws Exception { final String msg = ""; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java index c23e4e328..e67ada602 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java @@ -44,12 +44,17 @@ package org.eclipse.jgit.revwalk; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; @@ -441,12 +446,12 @@ public final PersonIdent getCommitterIdent() { * @return decoded commit message as a string. Never null. */ public final String getFullMessage() { - final byte[] raw = buffer; - final int msgB = RawParseUtils.commitMessage(raw, 0); - if (msgB < 0) + byte[] raw = buffer; + int msgB = RawParseUtils.commitMessage(raw, 0); + if (msgB < 0) { return ""; //$NON-NLS-1$ - final Charset enc = RawParseUtils.parseEncoding(raw); - return RawParseUtils.decode(enc, raw, msgB, raw.length); + } + return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length); } /** @@ -465,16 +470,17 @@ public final String getFullMessage() { * spanned multiple lines. Embedded LFs are converted to spaces. */ public final String getShortMessage() { - final byte[] raw = buffer; - final int msgB = RawParseUtils.commitMessage(raw, 0); - if (msgB < 0) + byte[] raw = buffer; + int msgB = RawParseUtils.commitMessage(raw, 0); + if (msgB < 0) { return ""; //$NON-NLS-1$ + } - final Charset enc = RawParseUtils.parseEncoding(raw); - final int msgE = RawParseUtils.endOfParagraph(raw, msgB); - String str = RawParseUtils.decode(enc, raw, msgB, msgE); - if (hasLF(raw, msgB, msgE)) + int msgE = RawParseUtils.endOfParagraph(raw, msgB); + String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE); + if (hasLF(raw, msgB, msgE)) { str = StringUtils.replaceLineBreaksWithSpace(str); + } return str; } @@ -485,6 +491,23 @@ static boolean hasLF(final byte[] r, int b, final int e) { return false; } + /** + * Determine the encoding of the commit message buffer. + *

+ * Locates the "encoding" header (if present) and returns its value. Due to + * corruption in the wild this may be an invalid encoding name that is not + * recognized by any character encoding library. + *

+ * If no encoding header is present, null. + * + * @return the preferred encoding of {@link #getRawBuffer()}; or null. + * @since 4.2 + */ + @Nullable + public final String getEncodingName() { + return RawParseUtils.parseEncodingName(buffer); + } + /** * Determine the encoding of the commit message buffer. *

@@ -492,14 +515,28 @@ static boolean hasLF(final byte[] r, int b, final int e) { * character set to apply to this buffer to evaluate its contents as * character data. *

- * If no encoding header is present, {@link Constants#CHARSET} is assumed. + * If no encoding header is present {@code UTF-8} is assumed. * * @return the preferred encoding of {@link #getRawBuffer()}. + * @throws IllegalCharsetNameException + * if the character set requested by the encoding header is + * malformed and unsupportable. + * @throws UnsupportedCharsetException + * if the JRE does not support the character set requested by + * the encoding header. */ public final Charset getEncoding() { return RawParseUtils.parseEncoding(buffer); } + private Charset guessEncoding() { + try { + return getEncoding(); + } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { + return UTF_8; + } + } + /** * Parse the footer lines (e.g. "Signed-off-by") for machine processing. *

@@ -529,7 +566,7 @@ public final List getFooterLines() { final int msgB = RawParseUtils.commitMessage(raw, 0); final ArrayList r = new ArrayList(4); - final Charset enc = getEncoding(); + final Charset enc = guessEncoding(); for (;;) { ptr = RawParseUtils.prevLF(raw, ptr); if (ptr <= msgB) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java index bf2785e0d..81a54bf7e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java @@ -45,8 +45,12 @@ package org.eclipse.jgit.revwalk; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; +import java.nio.charset.UnsupportedCharsetException; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; @@ -162,7 +166,7 @@ void parseCanonical(final RevWalk walk, final byte[] rawTag) int p = pos.value += 4; // "tag " final int nameEnd = RawParseUtils.nextLF(rawTag, p) - 1; - tagName = RawParseUtils.decode(Constants.CHARSET, rawTag, p, nameEnd); + tagName = RawParseUtils.decode(UTF_8, rawTag, p, nameEnd); if (walk.isRetainBody()) buffer = rawTag; @@ -207,12 +211,12 @@ public final PersonIdent getTaggerIdent() { * @return decoded tag message as a string. Never null. */ public final String getFullMessage() { - final byte[] raw = buffer; - final int msgB = RawParseUtils.tagMessage(raw, 0); - if (msgB < 0) + byte[] raw = buffer; + int msgB = RawParseUtils.tagMessage(raw, 0); + if (msgB < 0) { return ""; //$NON-NLS-1$ - final Charset enc = RawParseUtils.parseEncoding(raw); - return RawParseUtils.decode(enc, raw, msgB, raw.length); + } + return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length); } /** @@ -231,19 +235,28 @@ public final String getFullMessage() { * multiple lines. Embedded LFs are converted to spaces. */ public final String getShortMessage() { - final byte[] raw = buffer; - final int msgB = RawParseUtils.tagMessage(raw, 0); - if (msgB < 0) + byte[] raw = buffer; + int msgB = RawParseUtils.tagMessage(raw, 0); + if (msgB < 0) { return ""; //$NON-NLS-1$ + } - final Charset enc = RawParseUtils.parseEncoding(raw); - final int msgE = RawParseUtils.endOfParagraph(raw, msgB); - String str = RawParseUtils.decode(enc, raw, msgB, msgE); - if (RevCommit.hasLF(raw, msgB, msgE)) + int msgE = RawParseUtils.endOfParagraph(raw, msgB); + String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE); + if (RevCommit.hasLF(raw, msgB, msgE)) { str = StringUtils.replaceLineBreaksWithSpace(str); + } return str; } + private Charset guessEncoding() { + try { + return RawParseUtils.parseEncoding(buffer); + } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { + return UTF_8; + } + } + /** * Get a reference to the object this tag was placed on. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java index a20e0b060..f2955f7e6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java @@ -44,6 +44,8 @@ package org.eclipse.jgit.util; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.eclipse.jgit.lib.ObjectChecker.author; import static org.eclipse.jgit.lib.ObjectChecker.committer; import static org.eclipse.jgit.lib.ObjectChecker.encoding; @@ -60,6 +62,7 @@ import java.util.HashMap; import java.util.Map; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.PersonIdent; @@ -70,7 +73,7 @@ public final class RawParseUtils { * * @since 2.2 */ - public static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); //$NON-NLS-1$ + public static final Charset UTF8_CHARSET = UTF_8; private static final byte[] digits10; @@ -81,9 +84,9 @@ public final class RawParseUtils { private static final Map encodingAliases; static { - encodingAliases = new HashMap(); - encodingAliases.put("latin-1", Charset.forName("ISO-8859-1")); //$NON-NLS-1$ //$NON-NLS-2$ - encodingAliases.put("'utf8'", Charset.forName("UTF-8")); //$NON-NLS-1$ //$NON-NLS-2$ + encodingAliases = new HashMap<>(); + encodingAliases.put("latin-1", ISO_8859_1); //$NON-NLS-1$ + encodingAliases.put("iso-latin-1", ISO_8859_1); //$NON-NLS-1$ digits10 = new byte['9' + 1]; Arrays.fill(digits10, (byte) -1); @@ -671,6 +674,27 @@ public static final int encoding(final byte[] b, int ptr) { return match(b, ptr, encoding); } + /** + * Parse the "encoding " header as a string. + *

+ * Locates the "encoding " header (if present) and returns its value. + * + * @param b + * buffer to scan. + * @return the encoding header as specified in the commit; null if the + * header was not present and should be assumed. + * @since 4.2 + */ + @Nullable + public static String parseEncodingName(final byte[] b) { + int enc = encoding(b, 0); + if (enc < 0) { + return null; + } + int lf = nextLF(b, enc); + return decode(UTF_8, b, enc, lf - 1); + } + /** * Parse the "encoding " header into a character set reference. *

@@ -678,29 +702,33 @@ public static final int encoding(final byte[] b, int ptr) { * {@link #encoding(byte[], int)} and then returns the proper character set * to apply to this buffer to evaluate its contents as character data. *

- * If no encoding header is present, {@link Constants#CHARSET} is assumed. + * If no encoding header is present {@code UTF-8} is assumed. * * @param b * buffer to scan. * @return the Java character set representation. Never null. + * @throws IllegalCharsetNameException + * if the character set requested by the encoding header is + * malformed and unsupportable. + * @throws UnsupportedCharsetException + * if the JRE does not support the character set requested by + * the encoding header. */ public static Charset parseEncoding(final byte[] b) { - final int enc = encoding(b, 0); - if (enc < 0) - return Constants.CHARSET; - final int lf = nextLF(b, enc); - String decoded = decode(Constants.CHARSET, b, enc, lf - 1); + String enc = parseEncodingName(b); + if (enc == null) { + return UTF_8; + } + + String name = enc.trim(); try { - return Charset.forName(decoded); - } catch (IllegalCharsetNameException badName) { - Charset aliased = charsetForAlias(decoded); - if (aliased != null) - return aliased; - throw badName; - } catch (UnsupportedCharsetException badName) { - Charset aliased = charsetForAlias(decoded); - if (aliased != null) + return Charset.forName(name); + } catch (IllegalCharsetNameException + | UnsupportedCharsetException badName) { + Charset aliased = charsetForAlias(name); + if (aliased != null) { return aliased; + } throw badName; } } @@ -739,7 +767,15 @@ public static PersonIdent parsePersonIdent(final String in) { * parsed. */ public static PersonIdent parsePersonIdent(final byte[] raw, final int nameB) { - final Charset cs = parseEncoding(raw); + Charset cs; + try { + cs = parseEncoding(raw); + } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { + // Assume UTF-8 for person identities, usually this is correct. + // If not decode() will fall back to the ISO-8859-1 encoding. + cs = UTF_8; + } + final int emailB = nextLF(raw, nameB, '<'); final int emailE = nextLF(raw, emailB, '>'); if (emailB >= raw.length || raw[emailB] == '\n' || @@ -887,7 +923,7 @@ public static String decode(final byte[] buffer) { */ public static String decode(final byte[] buffer, final int start, final int end) { - return decode(Constants.CHARSET, buffer, start, end); + return decode(UTF_8, buffer, start, end); } /** @@ -961,23 +997,21 @@ public static String decode(final Charset cs, final byte[] buffer, public static String decodeNoFallback(final Charset cs, final byte[] buffer, final int start, final int end) throws CharacterCodingException { - final ByteBuffer b = ByteBuffer.wrap(buffer, start, end - start); + ByteBuffer b = ByteBuffer.wrap(buffer, start, end - start); b.mark(); // Try our built-in favorite. The assumption here is that // decoding will fail if the data is not actually encoded // using that encoder. - // try { - return decode(b, Constants.CHARSET); + return decode(b, UTF_8); } catch (CharacterCodingException e) { b.reset(); } - if (!cs.equals(Constants.CHARSET)) { + if (!cs.equals(UTF_8)) { // Try the suggested encoding, it might be right since it was // provided by the caller. - // try { return decode(b, cs); } catch (CharacterCodingException e) { @@ -987,9 +1021,8 @@ public static String decodeNoFallback(final Charset cs, // Try the default character set. A small group of people // might actually use the same (or very similar) locale. - // - final Charset defcs = Charset.defaultCharset(); - if (!defcs.equals(cs) && !defcs.equals(Constants.CHARSET)) { + Charset defcs = Charset.defaultCharset(); + if (!defcs.equals(cs) && !defcs.equals(UTF_8)) { try { return decode(b, defcs); } catch (CharacterCodingException e) { From 48e245fc606f7033b9de017d3dcae7b8ea7cc91a Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 27 Nov 2015 23:21:43 -0800 Subject: [PATCH 73/89] RefTreeDatabase: Ref database using refs/txn/committed Instead of storing references in the local filesystem rely on the RefTree rooted at refs/txn/committed. This avoids needing to store references in the packed-refs file by keeping all data rooted under a single refs/txn/committed ref. Performance to scan all references from a well packed RefTree is very close to reading the packed-refs file from local disk. Storing a packed RefTree is smaller due to pack file compression, about 49.39 bytes/ref (on average) compared to packed-refs using ~65.49 bytes/ref. Change-Id: I75caa631162dc127a780095066195cbacc746d49 --- .../storage/reftree/RefTreeDatabaseTest.java | 685 ++++++++++++++++++ .../storage/dfs/InMemoryRepository.java | 5 +- .../storage/reftree/AlwaysFailUpdate.java | 98 +++ .../internal/storage/reftree/Command.java | 2 +- .../internal/storage/reftree/RefTree.java | 14 +- .../storage/reftree/RefTreeBatch.java | 240 ++++++ .../storage/reftree/RefTreeDatabase.java | 314 ++++++++ .../storage/reftree/RefTreeRename.java | 121 ++++ .../storage/reftree/RefTreeUpdate.java | 176 +++++ .../internal/storage/reftree/Scanner.java | 286 ++++++++ 10 files changed, 1937 insertions(+), 4 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java new file mode 100644 index 000000000..020d1b1b5 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabaseTest.java @@ -0,0 +1,685 @@ +/* + * Copyright (C) 2010, 2013, 2016 Google Inc. + * 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.Constants.R_HEADS; +import static org.eclipse.jgit.lib.Constants.R_TAGS; +import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; +import static org.eclipse.jgit.lib.RefDatabase.ALL; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; +import org.junit.Before; +import org.junit.Test; + +public class RefTreeDatabaseTest { + private InMemRefTreeRepo repo; + private RefTreeDatabase refdb; + private RefDatabase bootstrap; + + private TestRepository testRepo; + private RevCommit A; + private RevCommit B; + private RevTag v1_0; + + @Before + public void setUp() throws Exception { + repo = new InMemRefTreeRepo(new DfsRepositoryDescription("test")); + bootstrap = refdb.getBootstrap(); + + testRepo = new TestRepository<>(repo); + A = testRepo.commit().create(); + B = testRepo.commit(testRepo.getRevWalk().parseCommit(A)); + v1_0 = testRepo.tag("v1_0", B); + testRepo.getRevWalk().parseBody(v1_0); + } + + @Test + public void testSupportsAtomic() { + assertTrue(refdb.performsAtomicTransactions()); + } + + @Test + public void testGetRefs_EmptyDatabase() throws IOException { + assertTrue("no references", refdb.getRefs(ALL).isEmpty()); + assertTrue("no references", refdb.getRefs(R_HEADS).isEmpty()); + assertTrue("no references", refdb.getRefs(R_TAGS).isEmpty()); + } + + @Test + public void testGetRefs_HeadOnOneBranch() throws IOException { + symref(HEAD, "refs/heads/master"); + update("refs/heads/master", A); + + Map all = refdb.getRefs(ALL); + assertEquals(2, all.size()); + assertTrue("has HEAD", all.containsKey(HEAD)); + assertTrue("has master", all.containsKey("refs/heads/master")); + + Ref head = all.get(HEAD); + Ref master = all.get("refs/heads/master"); + + assertEquals(HEAD, head.getName()); + assertTrue(head.isSymbolic()); + assertSame(LOOSE, head.getStorage()); + assertSame("uses same ref as target", master, head.getTarget()); + + assertEquals("refs/heads/master", master.getName()); + assertFalse(master.isSymbolic()); + assertSame(PACKED, master.getStorage()); + assertEquals(A, master.getObjectId()); + } + + @Test + public void testGetRefs_DetachedHead() throws IOException { + update(HEAD, A); + + Map all = refdb.getRefs(ALL); + assertEquals(1, all.size()); + assertTrue("has HEAD", all.containsKey(HEAD)); + + Ref head = all.get(HEAD); + assertEquals(HEAD, head.getName()); + assertFalse(head.isSymbolic()); + assertSame(PACKED, head.getStorage()); + assertEquals(A, head.getObjectId()); + } + + @Test + public void testGetRefs_DeeplyNestedBranch() throws IOException { + String name = "refs/heads/a/b/c/d/e/f/g/h/i/j/k"; + update(name, A); + + Map all = refdb.getRefs(ALL); + assertEquals(1, all.size()); + + Ref r = all.get(name); + assertEquals(name, r.getName()); + assertFalse(r.isSymbolic()); + assertSame(PACKED, r.getStorage()); + assertEquals(A, r.getObjectId()); + } + + @Test + public void testGetRefs_HeadBranchNotBorn() throws IOException { + update("refs/heads/A", A); + update("refs/heads/B", B); + + Map all = refdb.getRefs(ALL); + assertEquals(2, all.size()); + assertFalse("no HEAD", all.containsKey(HEAD)); + + Ref a = all.get("refs/heads/A"); + Ref b = all.get("refs/heads/B"); + + assertEquals(A, a.getObjectId()); + assertEquals(B, b.getObjectId()); + + assertEquals("refs/heads/A", a.getName()); + assertEquals("refs/heads/B", b.getName()); + } + + @Test + public void testGetRefs_HeadsOnly() throws IOException { + update("refs/heads/A", A); + update("refs/heads/B", B); + update("refs/tags/v1.0", v1_0); + + Map heads = refdb.getRefs(R_HEADS); + assertEquals(2, heads.size()); + + Ref a = heads.get("A"); + Ref b = heads.get("B"); + + assertEquals("refs/heads/A", a.getName()); + assertEquals("refs/heads/B", b.getName()); + + assertEquals(A, a.getObjectId()); + assertEquals(B, b.getObjectId()); + } + + @Test + public void testGetRefs_TagsOnly() throws IOException { + update("refs/heads/A", A); + update("refs/heads/B", B); + update("refs/tags/v1.0", v1_0); + + Map tags = refdb.getRefs(R_TAGS); + assertEquals(1, tags.size()); + + Ref a = tags.get("v1.0"); + assertEquals("refs/tags/v1.0", a.getName()); + assertEquals(v1_0, a.getObjectId()); + assertTrue(a.isPeeled()); + assertEquals(v1_0.getObject(), a.getPeeledObjectId()); + } + + @Test + public void testGetRefs_HeadsSymref() throws IOException { + symref("refs/heads/other", "refs/heads/master"); + update("refs/heads/master", A); + + Map heads = refdb.getRefs(R_HEADS); + assertEquals(2, heads.size()); + + Ref master = heads.get("master"); + Ref other = heads.get("other"); + + assertEquals("refs/heads/master", master.getName()); + assertEquals(A, master.getObjectId()); + + assertEquals("refs/heads/other", other.getName()); + assertEquals(A, other.getObjectId()); + assertSame(master, other.getTarget()); + } + + @Test + public void testGetRefs_InvalidPrefixes() throws IOException { + update("refs/heads/A", A); + + assertTrue("empty refs/heads", refdb.getRefs("refs/heads").isEmpty()); + assertTrue("empty objects", refdb.getRefs("objects").isEmpty()); + assertTrue("empty objects/", refdb.getRefs("objects/").isEmpty()); + } + + @Test + public void testGetRefs_DiscoversNew() throws IOException { + update("refs/heads/master", A); + Map orig = refdb.getRefs(ALL); + + update("refs/heads/next", B); + Map next = refdb.getRefs(ALL); + + assertEquals(1, orig.size()); + assertEquals(2, next.size()); + + assertFalse(orig.containsKey("refs/heads/next")); + assertTrue(next.containsKey("refs/heads/next")); + + assertEquals(A, next.get("refs/heads/master").getObjectId()); + assertEquals(B, next.get("refs/heads/next").getObjectId()); + } + + @Test + public void testGetRefs_DiscoversModified() throws IOException { + symref(HEAD, "refs/heads/master"); + update("refs/heads/master", A); + + Map all = refdb.getRefs(ALL); + assertEquals(A, all.get(HEAD).getObjectId()); + + update("refs/heads/master", B); + all = refdb.getRefs(ALL); + assertEquals(B, all.get(HEAD).getObjectId()); + assertEquals(B, refdb.exactRef(HEAD).getObjectId()); + } + + @Test + public void testGetRefs_CycleInSymbolicRef() throws IOException { + symref("refs/1", "refs/2"); + symref("refs/2", "refs/3"); + symref("refs/3", "refs/4"); + symref("refs/4", "refs/5"); + symref("refs/5", "refs/end"); + update("refs/end", A); + + Map all = refdb.getRefs(ALL); + Ref r = all.get("refs/1"); + assertNotNull("has 1", r); + + assertEquals("refs/1", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/2", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/3", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/4", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/5", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + r = r.getTarget(); + assertEquals("refs/end", r.getName()); + assertEquals(A, r.getObjectId()); + assertFalse(r.isSymbolic()); + + symref("refs/5", "refs/6"); + symref("refs/6", "refs/end"); + all = refdb.getRefs(ALL); + assertNull("mising 1 due to cycle", all.get("refs/1")); + assertEquals(A, all.get("refs/2").getObjectId()); + assertEquals(A, all.get("refs/3").getObjectId()); + assertEquals(A, all.get("refs/4").getObjectId()); + assertEquals(A, all.get("refs/5").getObjectId()); + assertEquals(A, all.get("refs/6").getObjectId()); + assertEquals(A, all.get("refs/end").getObjectId()); + } + + @Test + public void testGetRef_NonExistingBranchConfig() throws IOException { + assertNull("find branch config", refdb.getRef("config")); + assertNull("find branch config", refdb.getRef("refs/heads/config")); + } + + @Test + public void testGetRef_FindBranchConfig() throws IOException { + update("refs/heads/config", A); + + for (String t : new String[] { "config", "refs/heads/config" }) { + Ref r = refdb.getRef(t); + assertNotNull("find branch config (" + t + ")", r); + assertEquals("for " + t, "refs/heads/config", r.getName()); + assertEquals("for " + t, A, r.getObjectId()); + } + } + + @Test + public void testFirstExactRef() throws IOException { + update("refs/heads/A", A); + update("refs/tags/v1.0", v1_0); + + Ref a = refdb.firstExactRef("refs/heads/A", "refs/tags/v1.0"); + Ref one = refdb.firstExactRef("refs/tags/v1.0", "refs/heads/A"); + + assertEquals("refs/heads/A", a.getName()); + assertEquals("refs/tags/v1.0", one.getName()); + + assertEquals(A, a.getObjectId()); + assertEquals(v1_0, one.getObjectId()); + } + + @Test + public void testExactRef_DiscoversModified() throws IOException { + symref(HEAD, "refs/heads/master"); + update("refs/heads/master", A); + assertEquals(A, refdb.exactRef(HEAD).getObjectId()); + + update("refs/heads/master", B); + assertEquals(B, refdb.exactRef(HEAD).getObjectId()); + } + + @Test + public void testIsNameConflicting() throws IOException { + update("refs/heads/a/b", A); + update("refs/heads/q", B); + + // new references cannot replace an existing container + assertTrue(refdb.isNameConflicting("refs")); + assertTrue(refdb.isNameConflicting("refs/heads")); + assertTrue(refdb.isNameConflicting("refs/heads/a")); + + // existing reference is not conflicting + assertFalse(refdb.isNameConflicting("refs/heads/a/b")); + + // new references are not conflicting + assertFalse(refdb.isNameConflicting("refs/heads/a/d")); + assertFalse(refdb.isNameConflicting("refs/heads/master")); + + // existing reference must not be used as a container + assertTrue(refdb.isNameConflicting("refs/heads/a/b/c")); + assertTrue(refdb.isNameConflicting("refs/heads/q/master")); + + // refs/txn/ names always conflict. + assertTrue(refdb.isNameConflicting(refdb.getTxnCommitted())); + assertTrue(refdb.isNameConflicting("refs/txn/foo")); + } + + @Test + public void testUpdate_RefusesRefsTxnNamespace() throws IOException { + ObjectId txnId = getTxnCommitted(); + + RefUpdate u = refdb.newUpdate("refs/txn/tmp", false); + u.setNewObjectId(B); + assertEquals(RefUpdate.Result.LOCK_FAILURE, u.update()); + assertEquals(txnId, getTxnCommitted()); + + ReceiveCommand cmd = command(null, B, "refs/txn/tmp"); + BatchRefUpdate batch = refdb.newBatchUpdate(); + batch.addCommand(cmd); + batch.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + + assertEquals(REJECTED_OTHER_REASON, cmd.getResult()); + assertEquals(MessageFormat.format(JGitText.get().invalidRefName, + "refs/txn/tmp"), cmd.getMessage()); + assertEquals(txnId, getTxnCommitted()); + } + + @Test + public void testUpdate_RefusesDotLockInRefName() throws IOException { + ObjectId txnId = getTxnCommitted(); + + RefUpdate u = refdb.newUpdate("refs/heads/pu.lock", false); + u.setNewObjectId(B); + assertEquals(RefUpdate.Result.REJECTED, u.update()); + assertEquals(txnId, getTxnCommitted()); + + ReceiveCommand cmd = command(null, B, "refs/heads/pu.lock"); + BatchRefUpdate batch = refdb.newBatchUpdate(); + batch.addCommand(cmd); + batch.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + + assertEquals(REJECTED_OTHER_REASON, cmd.getResult()); + assertEquals(JGitText.get().funnyRefname, cmd.getMessage()); + assertEquals(txnId, getTxnCommitted()); + } + + @Test + public void testBatchRefUpdate_NonFastForwardAborts() throws IOException { + update("refs/heads/master", A); + update("refs/heads/masters", B); + ObjectId txnId = getTxnCommitted(); + + List commands = Arrays.asList( + command(A, B, "refs/heads/master"), + command(B, A, "refs/heads/masters")); + BatchRefUpdate batchUpdate = refdb.newBatchUpdate(); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + assertEquals(txnId, getTxnCommitted()); + + assertEquals(REJECTED_NONFASTFORWARD, + commands.get(1).getResult()); + assertEquals(REJECTED_OTHER_REASON, + commands.get(0).getResult()); + assertEquals(JGitText.get().transactionAborted, + commands.get(0).getMessage()); + } + + @Test + public void testBatchRefUpdate_ForceUpdate() throws IOException { + update("refs/heads/master", A); + update("refs/heads/masters", B); + ObjectId txnId = getTxnCommitted(); + + List commands = Arrays.asList( + command(A, B, "refs/heads/master"), + command(B, A, "refs/heads/masters")); + BatchRefUpdate batchUpdate = refdb.newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + assertNotEquals(txnId, getTxnCommitted()); + + Map refs = refdb.getRefs(ALL); + assertEquals(OK, commands.get(0).getResult()); + assertEquals(OK, commands.get(1).getResult()); + assertEquals( + "[refs/heads/master, refs/heads/masters]", + refs.keySet().toString()); + assertEquals(B.getId(), refs.get("refs/heads/master").getObjectId()); + assertEquals(A.getId(), refs.get("refs/heads/masters").getObjectId()); + } + + @Test + public void testBatchRefUpdate_NonFastForwardDoesNotDoExpensiveMergeCheck() + throws IOException { + update("refs/heads/master", B); + ObjectId txnId = getTxnCommitted(); + + List commands = Arrays.asList( + command(B, A, "refs/heads/master")); + BatchRefUpdate batchUpdate = refdb.newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(repo) { + @Override + public boolean isMergedInto(RevCommit base, RevCommit tip) { + fail("isMergedInto() should not be called"); + return false; + } + }, NullProgressMonitor.INSTANCE); + assertNotEquals(txnId, getTxnCommitted()); + + Map refs = refdb.getRefs(ALL); + assertEquals(OK, commands.get(0).getResult()); + assertEquals(A.getId(), refs.get("refs/heads/master").getObjectId()); + } + + @Test + public void testBatchRefUpdate_ConflictCausesAbort() throws IOException { + update("refs/heads/master", A); + update("refs/heads/masters", B); + ObjectId txnId = getTxnCommitted(); + + List commands = Arrays.asList( + command(A, B, "refs/heads/master"), + command(null, A, "refs/heads/master/x"), + command(null, A, "refs/heads")); + BatchRefUpdate batchUpdate = refdb.newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + assertEquals(txnId, getTxnCommitted()); + + assertEquals(LOCK_FAILURE, commands.get(0).getResult()); + + assertEquals(REJECTED_OTHER_REASON, commands.get(1).getResult()); + assertEquals(JGitText.get().transactionAborted, + commands.get(1).getMessage()); + + assertEquals(REJECTED_OTHER_REASON, commands.get(2).getResult()); + assertEquals(JGitText.get().transactionAborted, + commands.get(2).getMessage()); + } + + @Test + public void testBatchRefUpdate_NoConflictIfDeleted() throws IOException { + update("refs/heads/master", A); + update("refs/heads/masters", B); + ObjectId txnId = getTxnCommitted(); + + List commands = Arrays.asList( + command(A, B, "refs/heads/master"), + command(null, A, "refs/heads/masters/x"), + command(B, null, "refs/heads/masters")); + BatchRefUpdate batchUpdate = refdb.newBatchUpdate(); + batchUpdate.setAllowNonFastForwards(true); + batchUpdate.addCommand(commands); + batchUpdate.execute(new RevWalk(repo), NullProgressMonitor.INSTANCE); + assertNotEquals(txnId, getTxnCommitted()); + + assertEquals(OK, commands.get(0).getResult()); + assertEquals(OK, commands.get(1).getResult()); + assertEquals(OK, commands.get(2).getResult()); + + Map refs = refdb.getRefs(ALL); + assertEquals( + "[refs/heads/master, refs/heads/masters/x]", + refs.keySet().toString()); + assertEquals(A.getId(), refs.get("refs/heads/masters/x").getObjectId()); + } + + private ObjectId getTxnCommitted() throws IOException { + Ref r = bootstrap.exactRef(refdb.getTxnCommitted()); + if (r != null && r.getObjectId() != null) { + return r.getObjectId(); + } + return ObjectId.zeroId(); + } + + private static ReceiveCommand command(AnyObjectId a, AnyObjectId b, + String name) { + return new ReceiveCommand( + a != null ? a.copy() : ObjectId.zeroId(), + b != null ? b.copy() : ObjectId.zeroId(), + name); + } + + private void symref(final String name, final String dst) + throws IOException { + commit(new Function() { + @Override + public boolean apply(ObjectReader reader, RefTree tree) + throws IOException { + Ref old = tree.exactRef(reader, name); + Command n = new Command( + old, + new SymbolicRef( + name, + new ObjectIdRef.Unpeeled(Ref.Storage.NEW, dst, null))); + return tree.apply(Collections.singleton(n)); + } + }); + } + + private void update(final String name, final ObjectId id) + throws IOException { + commit(new Function() { + @Override + public boolean apply(ObjectReader reader, RefTree tree) + throws IOException { + Ref old = tree.exactRef(reader, name); + Command n; + try (RevWalk rw = new RevWalk(repo)) { + n = new Command(old, Command.toRef(rw, id, name, true)); + } + return tree.apply(Collections.singleton(n)); + } + }); + } + + interface Function { + boolean apply(ObjectReader reader, RefTree tree) throws IOException; + } + + private void commit(Function fun) throws IOException { + try (ObjectReader reader = repo.newObjectReader(); + ObjectInserter inserter = repo.newObjectInserter(); + RevWalk rw = new RevWalk(reader)) { + RefUpdate u = bootstrap.newUpdate(refdb.getTxnCommitted(), false); + CommitBuilder cb = new CommitBuilder(); + testRepo.setAuthorAndCommitter(cb); + + Ref ref = bootstrap.exactRef(refdb.getTxnCommitted()); + RefTree tree; + if (ref != null && ref.getObjectId() != null) { + tree = RefTree.read(reader, rw.parseTree(ref.getObjectId())); + cb.setParentId(ref.getObjectId()); + u.setExpectedOldObjectId(ref.getObjectId()); + } else { + tree = RefTree.newEmptyTree(); + u.setExpectedOldObjectId(ObjectId.zeroId()); + } + + assertTrue(fun.apply(reader, tree)); + cb.setTreeId(tree.writeTree(inserter)); + u.setNewObjectId(inserter.insert(cb)); + inserter.flush(); + switch (u.update(rw)) { + case NEW: + case FAST_FORWARD: + break; + default: + fail("Expected " + u.getName() + " to update"); + } + } + } + + private class InMemRefTreeRepo extends InMemoryRepository { + private final RefTreeDatabase refs; + + InMemRefTreeRepo(DfsRepositoryDescription repoDesc) { + super(repoDesc); + refs = new RefTreeDatabase(this, super.getRefDatabase(), + "refs/txn/committed"); + RefTreeDatabaseTest.this.refdb = refs; + } + + public RefDatabase getRefDatabase() { + return refs; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java index 205d3c7f8..a050e1a5b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java @@ -24,6 +24,7 @@ import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; +import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; @@ -54,7 +55,7 @@ public InMemoryRepository build() throws IOException { static final AtomicInteger packId = new AtomicInteger(); private final DfsObjDatabase objdb; - private final DfsRefDatabase refdb; + private final RefDatabase refdb; private boolean performsAtomicTransactions = true; /** @@ -80,7 +81,7 @@ public DfsObjDatabase getObjectDatabase() { } @Override - public DfsRefDatabase getRefDatabase() { + public RefDatabase getRefDatabase() { return refdb; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java new file mode 100644 index 000000000..12ef8734c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.internal.storage.reftree; + +import java.io.IOException; + +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; + +/** Update that always rejects with {@code LOCK_FAILURE}. */ +class AlwaysFailUpdate extends RefUpdate { + private final RefTreeDatabase refdb; + + AlwaysFailUpdate(RefTreeDatabase refdb, String name) { + super(new ObjectIdRef.Unpeeled(Ref.Storage.NEW, name, null)); + this.refdb = refdb; + setCheckConflicting(false); + } + + @Override + protected RefDatabase getRefDatabase() { + return refdb; + } + + @Override + protected Repository getRepository() { + return refdb.getRepository(); + } + + @Override + protected boolean tryLock(boolean deref) throws IOException { + return false; + } + + @Override + protected void unlock() { + // No locks are held here. + } + + @Override + protected Result doUpdate(Result desiredResult) { + return Result.LOCK_FAILURE; + } + + @Override + protected Result doDelete(Result desiredResult) { + return Result.LOCK_FAILURE; + } + + @Override + protected Result doLink(String target) { + return Result.LOCK_FAILURE; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java index 9ac375f16..540c4384a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java @@ -132,7 +132,7 @@ public Command(RevWalk rw, ReceiveCommand cmd) this.cmd = cmd; } - private static Ref toRef(RevWalk rw, ObjectId id, String name, + static Ref toRef(RevWalk rw, ObjectId id, String name, boolean mustExist) throws MissingObjectException, IOException { if (ObjectId.zeroId().equals(id)) { return null; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java index 237f7e995..66c0be676 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.internal.storage.reftree; +import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; import static org.eclipse.jgit.lib.Constants.R_REFS; import static org.eclipse.jgit.lib.Constants.encode; @@ -80,6 +81,7 @@ import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.util.RawParseUtils; @@ -243,6 +245,11 @@ public boolean apply(Collection cmdList) { try { DirCacheEditor ed = contents.editor(); for (Command cmd : cmdList) { + if (!isValidRef(cmd)) { + cmd.setResult(REJECTED_OTHER_REASON, + JGitText.get().funnyRefname); + return abort(cmdList); + } apply(ed, cmd); } ed.finish(); @@ -263,6 +270,11 @@ public boolean apply(Collection cmdList) { } } + private static boolean isValidRef(Command cmd) { + String n = cmd.getRefName(); + return HEAD.equals(n) || Repository.isValidRefName(n); + } + private void apply(DirCacheEditor ed, final Command cmd) { String path = refPath(cmd.getRefName()); Ref oldRef = cmd.getOldRef(); @@ -358,7 +370,7 @@ public static String refName(String path) { return R_REFS + path; } - private static String refPath(String name) { + static String refPath(String name) { if (name.startsWith(R_REFS)) { return name.substring(R_REFS.length()); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java new file mode 100644 index 000000000..0cedea94d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; +import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE; +import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; + +/** Batch update a {@link RefTreeDatabase}. */ +class RefTreeBatch extends BatchRefUpdate { + private final RefTreeDatabase refdb; + private Ref src; + private ObjectId parentCommitId; + private ObjectId parentTreeId; + private RefTree tree; + private PersonIdent author; + private ObjectId newCommitId; + + RefTreeBatch(RefTreeDatabase refdb) { + super(refdb); + this.refdb = refdb; + } + + @Override + public void execute(RevWalk rw, ProgressMonitor monitor) + throws IOException { + List todo = new ArrayList<>(getCommands().size()); + for (ReceiveCommand c : getCommands()) { + if (!isAllowNonFastForwards()) { + if (c.getType() == UPDATE) { + c.updateType(rw); + } + if (c.getType() == UPDATE_NONFASTFORWARD) { + c.setResult(REJECTED_NONFASTFORWARD); + reject(); + return; + } + } + todo.add(new Command(rw, c)); + } + init(rw); + execute(rw, todo); + } + + private void reject() { + String aborted = JGitText.get().transactionAborted; + for (ReceiveCommand c : getCommands()) { + if (c.getResult() == NOT_ATTEMPTED) { + c.setResult(REJECTED_OTHER_REASON, aborted); + } + } + } + + void init(RevWalk rw) throws IOException { + src = refdb.getBootstrap().exactRef(refdb.getTxnCommitted()); + if (src != null && src.getObjectId() != null) { + RevCommit c = rw.parseCommit(src.getObjectId()); + parentCommitId = c; + parentTreeId = c.getTree(); + tree = RefTree.read(rw.getObjectReader(), c.getTree()); + } else { + parentCommitId = ObjectId.zeroId(); + parentTreeId = new ObjectInserter.Formatter() + .idFor(OBJ_TREE, new byte[] {}); + tree = RefTree.newEmptyTree(); + } + } + + @Nullable + Ref exactRef(ObjectReader reader, String name) throws IOException { + return tree.exactRef(reader, name); + } + + /** + * Execute an update from {@link RefTreeUpdate} or {@link RefTreeRename}. + * + * @param rw + * current RevWalk handling the update or rename. + * @param todo + * commands to execute. Must never be a bootstrap reference name. + * @throws IOException + * the storage system is unable to read or write data. + */ + void execute(RevWalk rw, List todo) throws IOException { + for (Command c : todo) { + if (c.getResult() != NOT_ATTEMPTED) { + reject(todo, JGitText.get().transactionAborted); + return; + } + if (refdb.conflictsWithBootstrap(c.getRefName())) { + c.setResult(REJECTED_OTHER_REASON, MessageFormat + .format(JGitText.get().invalidRefName, c.getRefName())); + reject(todo, JGitText.get().transactionAborted); + return; + } + } + + if (apply(todo) && newCommitId != null) { + commit(rw, todo); + } + } + + private boolean apply(List todo) throws IOException { + if (!tree.apply(todo)) { + // apply set rejection information on commands. + return false; + } + + Repository repo = refdb.getRepository(); + try (ObjectInserter ins = repo.newObjectInserter()) { + CommitBuilder b = new CommitBuilder(); + b.setTreeId(tree.writeTree(ins)); + if (parentTreeId.equals(b.getTreeId())) { + for (Command c : todo) { + c.setResult(OK); + } + return true; + } + if (!parentCommitId.equals(ObjectId.zeroId())) { + b.setParentId(parentCommitId); + } + + author = getRefLogIdent(); + if (author == null) { + author = new PersonIdent(repo); + } + b.setAuthor(author); + b.setCommitter(author); + b.setMessage(getRefLogMessage()); + newCommitId = ins.insert(b); + ins.flush(); + } + return true; + } + + private void commit(RevWalk rw, List todo) throws IOException { + ReceiveCommand commit = new ReceiveCommand( + parentCommitId, newCommitId, + refdb.getTxnCommitted()); + updateBootstrap(rw, commit); + + if (commit.getResult() == OK) { + for (Command c : todo) { + c.setResult(OK); + } + } else { + reject(todo, commit.getResult().name()); + } + } + + private void updateBootstrap(RevWalk rw, ReceiveCommand commit) + throws IOException { + BatchRefUpdate u = refdb.getBootstrap().newBatchUpdate(); + u.setAllowNonFastForwards(true); + u.setPushCertificate(getPushCertificate()); + if (isRefLogDisabled()) { + u.disableRefLog(); + } else { + u.setRefLogIdent(author); + u.setRefLogMessage(getRefLogMessage(), false); + } + u.addCommand(commit); + u.execute(rw, NullProgressMonitor.INSTANCE); + } + + private static void reject(List todo, String msg) { + for (Command c : todo) { + if (c.getResult() == NOT_ATTEMPTED) { + c.setResult(REJECTED_OTHER_REASON, msg); + msg = JGitText.get().transactionAborted; + } + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java new file mode 100644 index 000000000..983216e30 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Ref.Storage; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefRename; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.util.RefList; +import org.eclipse.jgit.util.RefMap; + +/** + * Reference database backed by a {@link RefTree}. + *

+ * The storage for RefTreeDatabase has two parts. The main part is a native Git + * tree object stored under the {@code refs/txn} namespace. To avoid cycles, + * references to {@code refs/txn} are not stored in that tree object, but + * instead in a "bootstrap" layer, which is a separate {@link RefDatabase} such + * as {@link org.eclipse.jgit.internal.storage.file.RefDirectory} using local + * reference files inside of {@code $GIT_DIR/refs}. + */ +public class RefTreeDatabase extends RefDatabase { + private final Repository repo; + private final RefDatabase bootstrap; + private final String txnCommitted; + + @Nullable + private final String txnNamespace; + private volatile Scanner.Result refs; + + /** + * Create a RefTreeDb for a repository. + * + * @param repo + * the repository using references in this database. + * @param bootstrap + * bootstrap reference database storing the references that + * anchor the {@link RefTree}. + * @param txnCommitted + * name of the bootstrap reference holding the committed RefTree. + */ + public RefTreeDatabase(Repository repo, RefDatabase bootstrap, + String txnCommitted) { + this.repo = repo; + this.bootstrap = bootstrap; + this.txnNamespace = initNamespace(txnCommitted); + this.txnCommitted = txnCommitted; + } + + private static String initNamespace(String committed) { + int s = committed.lastIndexOf('/'); + if (s < 0) { + return null; + } + return committed.substring(0, s + 1); // Keep trailing '/'. + } + + Repository getRepository() { + return repo; + } + + /** + * @return the bootstrap reference database, which must be used to access + * {@link #getTxnCommitted()}, {@link #getTxnNamespace()}. + */ + public RefDatabase getBootstrap() { + return bootstrap; + } + + /** @return name of bootstrap reference anchoring committed RefTree. */ + public String getTxnCommitted() { + return txnCommitted; + } + + /** + * @return namespace used by bootstrap layer, e.g. {@code refs/txn/}. + * Always ends in {@code '/'}. + */ + @Nullable + public String getTxnNamespace() { + return txnNamespace; + } + + @Override + public void create() throws IOException { + bootstrap.create(); + } + + @Override + public boolean performsAtomicTransactions() { + return true; + } + + @Override + public void refresh() { + bootstrap.refresh(); + } + + @Override + public void close() { + refs = null; + bootstrap.close(); + } + + @Override + public Ref getRef(String name) throws IOException { + return findRef(getRefs(ALL), name); + } + + @Override + public Ref exactRef(String name) throws IOException { + if (conflictsWithBootstrap(name)) { + return null; + } + + boolean partial = false; + Ref src = bootstrap.exactRef(txnCommitted); + Scanner.Result c = refs; + if (c == null || !c.refTreeId.equals(idOf(src))) { + c = Scanner.scanRefTree(repo, src, prefixOf(name), false); + partial = true; + } + + Ref r = c.all.get(name); + if (r != null && r.isSymbolic()) { + r = c.sym.get(name); + if (partial && r.getObjectId() == null) { + // Attempting exactRef("HEAD") with partial scan will leave + // an unresolved symref as its target e.g. refs/heads/master + // was not read by the partial scan. Scan everything instead. + return getRefs(ALL).get(name); + } + } + return r; + } + + private static String prefixOf(String name) { + int s = name.lastIndexOf('/'); + if (s >= 0) { + return name.substring(0, s); + } + return ""; //$NON-NLS-1$ + } + + @Override + public Map getRefs(String prefix) throws IOException { + if (!prefix.isEmpty() && prefix.charAt(prefix.length() - 1) != '/') { + return new HashMap<>(0); + } + + Ref src = bootstrap.exactRef(txnCommitted); + Scanner.Result c = refs; + if (c == null || !c.refTreeId.equals(idOf(src))) { + c = Scanner.scanRefTree(repo, src, prefix, true); + if (prefix.isEmpty()) { + refs = c; + } + } + return new RefMap(prefix, RefList. emptyList(), c.all, c.sym); + } + + private static ObjectId idOf(@Nullable Ref src) { + return src != null && src.getObjectId() != null + ? src.getObjectId() + : ObjectId.zeroId(); + } + + @Override + public List getAdditionalRefs() throws IOException { + return Collections.emptyList(); + } + + @Override + public Ref peel(Ref ref) throws IOException { + Ref i = ref.getLeaf(); + ObjectId id = i.getObjectId(); + if (i.isPeeled() || id == null) { + return ref; + } + try (RevWalk rw = new RevWalk(repo)) { + RevObject obj = rw.parseAny(id); + if (obj instanceof RevTag) { + ObjectId p = rw.peel(obj).copy(); + i = new ObjectIdRef.PeeledTag(PACKED, i.getName(), id, p); + } else { + i = new ObjectIdRef.PeeledNonTag(PACKED, i.getName(), id); + } + } + return recreate(ref, i); + } + + private static Ref recreate(Ref old, Ref leaf) { + if (old.isSymbolic()) { + Ref dst = recreate(old.getTarget(), leaf); + return new SymbolicRef(old.getName(), dst); + } + return leaf; + } + + @Override + public boolean isNameConflicting(String name) throws IOException { + return conflictsWithBootstrap(name) + || !getConflictingNames(name).isEmpty(); + } + + @Override + public BatchRefUpdate newBatchUpdate() { + return new RefTreeBatch(this); + } + + @Override + public RefUpdate newUpdate(String name, boolean detach) throws IOException { + if (conflictsWithBootstrap(name)) { + return new AlwaysFailUpdate(this, name); + } + + Ref r = exactRef(name); + if (r == null) { + r = new ObjectIdRef.Unpeeled(Storage.NEW, name, null); + } + + boolean detaching = detach && r.isSymbolic(); + if (detaching) { + r = new ObjectIdRef.Unpeeled(LOOSE, name, r.getObjectId()); + } + + RefTreeUpdate u = new RefTreeUpdate(this, r); + if (detaching) { + u.setDetachingSymbolicRef(); + } + return u; + } + + @Override + public RefRename newRename(String fromName, String toName) + throws IOException { + RefUpdate from = newUpdate(fromName, true); + RefUpdate to = newUpdate(toName, true); + return new RefTreeRename(this, from, to); + } + + boolean conflictsWithBootstrap(String name) { + if (txnNamespace != null && name.startsWith(txnNamespace)) { + return true; + } else if (txnCommitted.equals(name)) { + return true; + } else if (name.length() > txnCommitted.length() + && name.charAt(txnCommitted.length()) == '/' + && name.startsWith(txnCommitted)) { + return true; + } + return false; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java new file mode 100644 index 000000000..5fd7ecdd7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Constants.HEAD; +import static org.eclipse.jgit.lib.RefUpdate.Result.REJECTED; +import static org.eclipse.jgit.lib.RefUpdate.Result.RENAMED; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefRename; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevWalk; + +/** Single reference rename to {@link RefTreeDatabase}. */ +class RefTreeRename extends RefRename { + private final RefTreeDatabase refdb; + + RefTreeRename(RefTreeDatabase refdb, RefUpdate src, RefUpdate dst) { + super(src, dst); + this.refdb = refdb; + } + + @Override + protected Result doRename() throws IOException { + try (RevWalk rw = new RevWalk(refdb.getRepository())) { + RefTreeBatch batch = new RefTreeBatch(refdb); + batch.setRefLogIdent(getRefLogIdent()); + batch.setRefLogMessage(getRefLogMessage(), false); + batch.init(rw); + + Ref head = batch.exactRef(rw.getObjectReader(), HEAD); + Ref oldRef = batch.exactRef(rw.getObjectReader(), source.getName()); + if (oldRef == null) { + return REJECTED; + } + + Ref newRef = asNew(oldRef); + List mv = new ArrayList<>(3); + mv.add(new Command(oldRef, null)); + mv.add(new Command(null, newRef)); + if (head != null && head.isSymbolic() + && head.getTarget().getName().equals(oldRef.getName())) { + mv.add(new Command( + head, + new SymbolicRef(head.getName(), newRef))); + } + batch.execute(rw, mv); + return RefTreeUpdate.translate(mv.get(1).getResult(), RENAMED); + } + } + + private Ref asNew(Ref src) { + String name = destination.getName(); + if (src.isSymbolic()) { + return new SymbolicRef(name, src.getTarget()); + } + + ObjectId peeled = src.getPeeledObjectId(); + if (peeled != null) { + return new ObjectIdRef.PeeledTag( + src.getStorage(), + name, + src.getObjectId(), + peeled); + } + + return new ObjectIdRef.PeeledNonTag( + src.getStorage(), + name, + src.getObjectId()); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java new file mode 100644 index 000000000..8829c1156 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.Ref.Storage.LOOSE; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; + +import java.io.IOException; +import java.util.Collections; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; + +/** Single reference update to {@link RefTreeDatabase}. */ +class RefTreeUpdate extends RefUpdate { + private final RefTreeDatabase refdb; + private RevWalk rw; + private RefTreeBatch batch; + private Ref oldRef; + + RefTreeUpdate(RefTreeDatabase refdb, Ref ref) { + super(ref); + this.refdb = refdb; + setCheckConflicting(false); // Done automatically by doUpdate. + } + + @Override + protected RefDatabase getRefDatabase() { + return refdb; + } + + @Override + protected Repository getRepository() { + return refdb.getRepository(); + } + + @Override + protected boolean tryLock(boolean deref) throws IOException { + rw = new RevWalk(getRepository()); + batch = new RefTreeBatch(refdb); + batch.init(rw); + oldRef = batch.exactRef(rw.getObjectReader(), getName()); + if (oldRef != null && oldRef.getObjectId() != null) { + setOldObjectId(oldRef.getObjectId()); + } else if (oldRef == null && getExpectedOldObjectId() != null) { + setOldObjectId(ObjectId.zeroId()); + } + return true; + } + + @Override + protected void unlock() { + batch = null; + if (rw != null) { + rw.close(); + rw = null; + } + } + + @Override + protected Result doUpdate(Result desiredResult) throws IOException { + return run(newRef(getName(), getNewObjectId()), desiredResult); + } + + private Ref newRef(String name, ObjectId id) + throws MissingObjectException, IOException { + RevObject o = rw.parseAny(id); + if (o instanceof RevTag) { + RevObject p = rw.peel(o); + return new ObjectIdRef.PeeledTag(LOOSE, name, id, p.copy()); + } + return new ObjectIdRef.PeeledNonTag(LOOSE, name, id); + } + + @Override + protected Result doDelete(Result desiredResult) throws IOException { + return run(null, desiredResult); + } + + @Override + protected Result doLink(String target) throws IOException { + Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null); + SymbolicRef n = new SymbolicRef(getName(), dst); + Result desiredResult = getRef().getStorage() == NEW + ? Result.NEW + : Result.FORCED; + return run(n, desiredResult); + } + + private Result run(@Nullable Ref newRef, Result desiredResult) + throws IOException { + Command c = new Command(oldRef, newRef); + batch.setRefLogIdent(getRefLogIdent()); + batch.setRefLogMessage(getRefLogMessage(), isRefLogIncludingResult()); + batch.execute(rw, Collections.singletonList(c)); + return translate(c.getResult(), desiredResult); + } + + static Result translate(ReceiveCommand.Result r, Result desiredResult) { + switch (r) { + case OK: + return desiredResult; + + case LOCK_FAILURE: + return Result.LOCK_FAILURE; + + case NOT_ATTEMPTED: + return Result.NOT_ATTEMPTED; + + case REJECTED_MISSING_OBJECT: + return Result.IO_FAILURE; + + case REJECTED_CURRENT_BRANCH: + return Result.REJECTED_CURRENT_BRANCH; + + case REJECTED_OTHER_REASON: + case REJECTED_NOCREATE: + case REJECTED_NODELETE: + case REJECTED_NONFASTFORWARD: + default: + return Result.REJECTED; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java new file mode 100644 index 000000000..b3aeed0df --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.R_REFS; +import static org.eclipse.jgit.lib.Constants.encode; +import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; +import static org.eclipse.jgit.lib.Ref.Storage.NEW; +import static org.eclipse.jgit.lib.Ref.Storage.PACKED; + +import java.io.IOException; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.util.Paths; +import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.RefList; + +/** A tree parser that extracts references from a {@link RefTree}. */ +class Scanner { + private static final int MAX_SYMLINK_BYTES = 10 << 10; + private static final byte[] BINARY_R_REFS = encode(R_REFS); + private static final byte[] REFS_DOT_DOT = encode("refs/.."); //$NON-NLS-1$ + + static class Result { + final ObjectId refTreeId; + final RefList all; + final RefList sym; + + Result(ObjectId id, RefList all, RefList sym) { + this.refTreeId = id; + this.all = all; + this.sym = sym; + } + } + + /** + * Scan a {@link RefTree} and parse entries into {@link Ref} instances. + * + * @param repo + * source repository containing the commit and tree objects that + * make up the RefTree. + * @param src + * bootstrap reference such as {@code refs/txn/committed} to read + * the reference tree tip from. The current ObjectId will be + * included in {@link Result#refTreeId}. + * @param prefix + * if non-empty a reference prefix to scan only a subdirectory. + * For example {@code prefix = "refs/heads/"} will limit the scan + * to only the {@code "heads"} directory of the RefTree, avoiding + * other directories like {@code "tags"}. Empty string reads all + * entries in the RefTree. + * @param recursive + * if true recurse into subdirectories of the reference tree; + * false to read only one level. Callers may use false during an + * implementation of {@code exactRef(String)} where only one + * reference is needed out of a specific subtree. + * @return sorted list of references after parsing. + * @throws IOException + * tree cannot be accessed from the repository. + */ + static Result scanRefTree(Repository repo, @Nullable Ref src, String prefix, + boolean recursive) throws IOException { + RefList.Builder all = new RefList.Builder<>(); + RefList.Builder sym = new RefList.Builder<>(); + + ObjectId srcId; + if (src != null && src.getObjectId() != null) { + try (ObjectReader reader = repo.newObjectReader()) { + srcId = src.getObjectId(); + scan(reader, srcId, prefix, recursive, all, sym); + } + } else { + srcId = ObjectId.zeroId(); + } + + RefList aList = all.toRefList(); + for (int idx = 0; idx < sym.size();) { + Ref s = sym.get(idx); + Ref r = resolve(s, 0, aList); + if (r != null) { + sym.set(idx++, r); + } else { + // Remove broken symbolic reference, they don't exist. + sym.remove(idx); + int rm = aList.find(s.getName()); + if (0 <= rm) { + aList = aList.remove(rm); + } + } + } + return new Result(srcId, aList, sym.toRefList()); + } + + private static void scan(ObjectReader reader, AnyObjectId srcId, + String prefix, boolean recursive, + RefList.Builder all, RefList.Builder sym) + throws IncorrectObjectTypeException, IOException { + CanonicalTreeParser p = createParserAtPath(reader, srcId, prefix); + if (p == null) { + return; + } + + while (!p.eof()) { + int mode = p.getEntryRawMode(); + if (mode == TYPE_TREE) { + if (recursive) { + p = p.createSubtreeIterator(reader); + } else { + p = p.next(); + } + continue; + } + + if (!curElementHasPeelSuffix(p)) { + Ref r = toRef(reader, mode, p); + if (r != null) { + all.add(r); + if (r.isSymbolic()) { + sym.add(r); + } + } + } else if (mode == TYPE_GITLINK) { + peel(all, p); + } + p = p.next(); + } + } + + private static CanonicalTreeParser createParserAtPath(ObjectReader reader, + AnyObjectId srcId, String prefix) throws IOException { + ObjectId root = toTree(reader, srcId); + if (prefix.isEmpty()) { + return new CanonicalTreeParser(BINARY_R_REFS, reader, root); + } + + String dir = RefTree.refPath(Paths.stripTrailingSeparator(prefix)); + TreeWalk tw = TreeWalk.forPath(reader, dir, root); + if (tw == null || !tw.isSubtree()) { + return null; + } + + ObjectId id = tw.getObjectId(0); + return new CanonicalTreeParser(encode(prefix), reader, id); + } + + private static Ref resolve(Ref ref, int depth, RefList refs) + throws IOException { + if (!ref.isSymbolic()) { + return ref; + } else if (MAX_SYMBOLIC_REF_DEPTH <= depth) { + return null; + } + + Ref r = refs.get(ref.getTarget().getName()); + if (r == null) { + return ref; + } + + Ref dst = resolve(r, depth + 1, refs); + if (dst == null) { + return null; + } + return new SymbolicRef(ref.getName(), dst); + } + + @SuppressWarnings("resource") + private static RevTree toTree(ObjectReader reader, AnyObjectId id) + throws IOException { + return new RevWalk(reader).parseTree(id); + } + + private static boolean curElementHasPeelSuffix(AbstractTreeIterator itr) { + int n = itr.getEntryPathLength(); + byte[] c = itr.getEntryPathBuffer(); + return n > 3 && c[n - 3] == '^' && c[n - 2] == '{' && c[n - 1] == '}'; + } + + private static void peel(RefList.Builder all, CanonicalTreeParser p) { + String name = refName(p, true); + for (int idx = all.size() - 1; 0 <= idx; idx--) { + Ref r = all.get(idx); + int cmp = r.getName().compareTo(name); + if (cmp == 0) { + all.set(idx, new ObjectIdRef.PeeledTag(PACKED, r.getName(), + r.getObjectId(), p.getEntryObjectId())); + break; + } else if (cmp < 0) { + // Stray peeled name without matching base name; skip entry. + break; + } + } + } + + private static Ref toRef(ObjectReader reader, int mode, + CanonicalTreeParser p) throws IOException { + if (mode == TYPE_GITLINK) { + String name = refName(p, false); + ObjectId id = p.getEntryObjectId(); + return new ObjectIdRef.PeeledNonTag(PACKED, name, id); + + } else if (mode == TYPE_SYMLINK) { + ObjectId id = p.getEntryObjectId(); + byte[] bin = reader.open(id, OBJ_BLOB) + .getCachedBytes(MAX_SYMLINK_BYTES); + String dst = RawParseUtils.decode(bin); + Ref trg = new ObjectIdRef.Unpeeled(NEW, dst, null); + String name = refName(p, false); + return new SymbolicRef(name, trg); + } + return null; + } + + private static String refName(CanonicalTreeParser p, boolean peel) { + byte[] buf = p.getEntryPathBuffer(); + int len = p.getEntryPathLength(); + if (peel) { + len -= 3; + } + int ptr = 0; + if (RawParseUtils.match(buf, ptr, REFS_DOT_DOT) > 0) { + ptr = 7; + } + return RawParseUtils.decode(buf, ptr, len); + } + + private Scanner() { + } +} From 088c2fc6e33737a406e69a99d75f06d3fbb05e30 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 9 Jan 2016 12:51:14 -0800 Subject: [PATCH 74/89] FileRepository: Support extensions.refsBackendType = RefTree This experimental code can be enabled in $GIT_DIR/config: [core] repositoryformatversion = 1 [extensions] refsBackendType = RefTree When these are set the repository will read references from the RefTree rooted by the $GIT_DIR/refs/txn/committed reference. Update debug-rebuild-ref-tree to rebuild refs/txn/committed only from the bootstrap layer. This avoids misuse by rebuilding using packed-refs and $GIT_DIR/refs tree. Change-Id: Icf600e4a36b2f7867822a7ab1f1617d73c710a4b --- .../jgit/pgm/debug/RebuildRefTree.java | 29 ++++++++++++++----- .../file/FileRepositoryBuilderTest.java | 2 +- .../internal/storage/file/FileRepository.java | 23 +++++++++++---- .../storage/reftree/RefTreeDatabase.java | 23 +++++++++++++++ 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java index 6af4ce1dd..78ca1a712 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildRefTree.java @@ -48,6 +48,7 @@ import java.util.Map; import org.eclipse.jgit.internal.storage.reftree.RefTree; +import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; @@ -59,12 +60,11 @@ import org.eclipse.jgit.pgm.Command; import org.eclipse.jgit.pgm.TextBuiltin; import org.eclipse.jgit.revwalk.RevWalk; -import org.kohsuke.args4j.Argument; @Command(usage = "usage_RebuildRefTree") class RebuildRefTree extends TextBuiltin { - @Argument(index = 0, required = true, metaVar = "metaVar_ref", usage = "usage_updateRef") - String refName; + private String txnNamespace; + private String txnCommitted; @Override protected void run() throws Exception { @@ -72,10 +72,25 @@ protected void run() throws Exception { RevWalk rw = new RevWalk(reader); ObjectInserter inserter = db.newObjectInserter()) { RefDatabase refDb = db.getRefDatabase(); + if (refDb instanceof RefTreeDatabase) { + RefTreeDatabase d = (RefTreeDatabase) refDb; + refDb = d.getBootstrap(); + txnNamespace = d.getTxnNamespace(); + txnCommitted = d.getTxnCommitted(); + } else { + RefTreeDatabase d = new RefTreeDatabase(db, refDb); + txnNamespace = d.getTxnNamespace(); + txnCommitted = d.getTxnCommitted(); + } + + errw.format("Rebuilding %s from %s", //$NON-NLS-1$ + txnCommitted, refDb.getClass().getSimpleName()); + errw.println(); + errw.flush(); CommitBuilder b = new CommitBuilder(); - Ref ref = db.getRefDatabase().exactRef(refName); - RefUpdate update = db.updateRef(refName); + Ref ref = refDb.exactRef(txnCommitted); + RefUpdate update = refDb.newUpdate(txnCommitted, true); ObjectId oldTreeId; if (ref != null && ref.getObjectId() != null) { @@ -116,10 +131,10 @@ private RefTree rebuild(Map refMap) { = new ArrayList<>(); for (Ref r : refMap.values()) { - if (refName.equals(r.getName())) { + if (r.getName().equals(txnCommitted) + || r.getName().startsWith(txnNamespace)) { continue; } - cmds.add(new org.eclipse.jgit.internal.storage.reftree.Command( null, db.peel(r))); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java index 280d6040c..6c8ee737d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileRepositoryBuilderTest.java @@ -104,7 +104,7 @@ public void unknownRepositoryFormatVersion() throws Exception { Repository r = createWorkRepository(); StoredConfig config = r.getConfig(); config.setLong(ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 1); + ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 999999); config.save(); try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java index 490cbcaa8..4f45cbee5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java @@ -63,6 +63,7 @@ import org.eclipse.jgit.events.ConfigChangedListener; import org.eclipse.jgit.events.IndexChangedEvent; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase; import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle; import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateRepository; import org.eclipse.jgit.lib.BaseRepositoryBuilder; @@ -201,7 +202,22 @@ public void onConfigChanged(ConfigChangedEvent event) { } }); - refs = new RefDirectory(this); + final long repositoryFormatVersion = getConfig().getLong( + ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0); + + String reftype = repoConfig.getString( + "extensions", null, "refsBackendType"); //$NON-NLS-1$ //$NON-NLS-2$ + if (repositoryFormatVersion >= 1 && reftype != null) { + if (StringUtils.equalsIgnoreCase(reftype, "reftree")) { //$NON-NLS-1$ + refs = new RefTreeDatabase(this, new RefDirectory(this)); + } else { + throw new IOException(JGitText.get().unknownRepositoryFormat); + } + } else { + refs = new RefDirectory(this); + } + objectDatabase = new ObjectDirectory(repoConfig, // options.getObjectDirectory(), // options.getAlternateObjectDirectories(), // @@ -209,10 +225,7 @@ public void onConfigChanged(ConfigChangedEvent event) { new File(getDirectory(), Constants.SHALLOW)); if (objectDatabase.exists()) { - final long repositoryFormatVersion = getConfig().getLong( - ConfigConstants.CONFIG_CORE_SECTION, null, - ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0); - if (repositoryFormatVersion > 0) + if (repositoryFormatVersion > 1) throw new IOException(MessageFormat.format( JGitText.get().unknownRepositoryFormat2, Long.valueOf(repositoryFormatVersion))); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java index 983216e30..dc6031110 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java @@ -54,6 +54,7 @@ import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.lib.BatchRefUpdate; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.Ref; @@ -88,6 +89,28 @@ public class RefTreeDatabase extends RefDatabase { private final String txnNamespace; private volatile Scanner.Result refs; + /** + * Create a RefTreeDb for a repository. + * + * @param repo + * the repository using references in this database. + * @param bootstrap + * bootstrap reference database storing the references that + * anchor the {@link RefTree}. + */ + public RefTreeDatabase(Repository repo, RefDatabase bootstrap) { + Config cfg = repo.getConfig(); + String committed = cfg.getString("reftree", null, "committedRef"); //$NON-NLS-1$ //$NON-NLS-2$ + if (committed == null || committed.isEmpty()) { + committed = "refs/txn/committed"; //$NON-NLS-1$ + } + + this.repo = repo; + this.bootstrap = bootstrap; + this.txnNamespace = initNamespace(committed); + this.txnCommitted = committed; + } + /** * Create a RefTreeDb for a repository. * From 398d8e877f91216e287a7a569ea758062dd7c521 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Sat, 9 Jan 2016 17:27:25 -0800 Subject: [PATCH 75/89] RefTree: Change peel suffix to " ^" (space carrot) Using ^{} as the peel suffix has caused problems when projects used tags like v2.1 and then v2.1.1, v2.2.2, etc. The peeled value for v2.1 was stored very far away in the tree relative to v2.1 itself as ^ sorts in the ASCII/UTF-8 encoding after all other common tag characters like digits and dots. Use " ^" instead as space is not valid in a reference name, sorts before all other valid reference characters (thus forcing next entry locality) and this looks like a peeled marker for the prior tag. Change-Id: I26d2247a0428dfe26a9c319c02159502b3a67455 --- .../eclipse/jgit/internal/storage/reftree/RefTree.java | 8 ++++---- .../eclipse/jgit/internal/storage/reftree/Scanner.java | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java index 66c0be676..43eec5185 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java @@ -100,15 +100,15 @@ * blob storing the name of the target reference. *

* Annotated tags also store the peeled object using a {@code GITLINK} entry - * with the suffix "^{}", for example {@code "tags/v1.0"} stores - * the annotated tag object, while "tags/v1.0^{}" stores the commit - * the tag annotates. + * with the suffix " ^" (space carrot), for example + * {@code "tags/v1.0"} stores the annotated tag object, while + * "tags/v1.0 ^" stores the commit the tag annotates. *

* {@code HEAD} is a special case and stored as {@code "..HEAD"}. */ public class RefTree { /** Suffix applied to GITLINK to indicate its the peeled value of a tag. */ - public static final String PEELED_SUFFIX = "^{}"; //$NON-NLS-1$ + public static final String PEELED_SUFFIX = " ^"; //$NON-NLS-1$ static final String ROOT_DOTDOT = ".."; //$NON-NLS-1$ /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java index b3aeed0df..d383abf31 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java @@ -230,7 +230,7 @@ private static RevTree toTree(ObjectReader reader, AnyObjectId id) private static boolean curElementHasPeelSuffix(AbstractTreeIterator itr) { int n = itr.getEntryPathLength(); byte[] c = itr.getEntryPathBuffer(); - return n > 3 && c[n - 3] == '^' && c[n - 2] == '{' && c[n - 1] == '}'; + return n > 2 && c[n - 2] == ' ' && c[n - 1] == '^'; } private static void peel(RefList.Builder all, CanonicalTreeParser p) { @@ -272,7 +272,7 @@ private static String refName(CanonicalTreeParser p, boolean peel) { byte[] buf = p.getEntryPathBuffer(); int len = p.getEntryPathLength(); if (peel) { - len -= 3; + len -= 2; } int ptr = 0; if (RawParseUtils.match(buf, ptr, REFS_DOT_DOT) > 0) { From 9e3d3919537d8bf18bcdd4fecd69ec46700e4ce1 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Mon, 11 Jan 2016 20:44:10 -0800 Subject: [PATCH 76/89] Change to extensions.refsStorage git-core just rerolled the extensible backends series with refsStorage as the configuration key. Update JGit to match git-core. Change-Id: If345a2403a996e358b29cfa2a2298f6e8d59d96b --- .../org/eclipse/jgit/internal/storage/file/FileRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java index 4f45cbee5..62d2d6969 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java @@ -207,7 +207,7 @@ public void onConfigChanged(ConfigChangedEvent event) { ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0); String reftype = repoConfig.getString( - "extensions", null, "refsBackendType"); //$NON-NLS-1$ //$NON-NLS-2$ + "extensions", null, "refsStorage"); //$NON-NLS-1$ //$NON-NLS-2$ if (repositoryFormatVersion >= 1 && reftype != null) { if (StringUtils.equalsIgnoreCase(reftype, "reftree")) { //$NON-NLS-1$ refs = new RefTreeDatabase(this, new RefDirectory(this)); From 40051505d7aaccfe2efaf5f3022f1d99a3976554 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Tue, 12 Jan 2016 10:50:36 -0800 Subject: [PATCH 77/89] GC: Pack RefTrees in their own pack The RefTree graph needs to be quickly accessed to read references. It is also distinct graph disconnected from the rest of the repository. Store the commit and tree objects in their own pack. Change-Id: Icbb735be8fa91ccbf0708ca3a219b364e11a6b83 --- .../storage/dfs/DfsGarbageCollector.java | 38 ++++-- .../internal/storage/dfs/DfsObjDatabase.java | 7 + .../jgit/internal/storage/file/GC.java | 61 +++++---- .../storage/reftree/RefTreeNames.java | 124 ++++++++++++++++++ 4 files changed, 197 insertions(+), 33 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java index bb51fc4ca..c48a49da7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java @@ -44,18 +44,18 @@ package org.eclipse.jgit.internal.storage.dfs; import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC; +import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_TXN; import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE; import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX; import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX; import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; -import static org.eclipse.jgit.lib.RefDatabase.ALL; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import org.eclipse.jgit.internal.JGitText; @@ -63,6 +63,7 @@ import org.eclipse.jgit.internal.storage.file.PackIndex; import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.internal.storage.pack.PackWriter; +import org.eclipse.jgit.internal.storage.reftree.RefTreeNames; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; @@ -94,14 +95,11 @@ public class DfsGarbageCollector { private long coalesceGarbageLimit = 50 << 20; - private Map refsBefore; - private List packsBefore; private Set allHeads; - private Set nonHeads; - + private Set txnHeads; private Set tagTargets; /** @@ -197,19 +195,22 @@ public boolean pack(ProgressMonitor pm) throws IOException { refdb.refresh(); objdb.clearCache(); - refsBefore = refdb.getRefs(ALL); + Collection refsBefore = RefTreeNames.allRefs(refdb); packsBefore = packsToRebuild(); if (packsBefore.isEmpty()) return true; allHeads = new HashSet(); nonHeads = new HashSet(); + txnHeads = new HashSet(); tagTargets = new HashSet(); - for (Ref ref : refsBefore.values()) { + for (Ref ref : refsBefore) { if (ref.isSymbolic() || ref.getObjectId() == null) continue; if (isHead(ref)) allHeads.add(ref.getObjectId()); + else if (RefTreeNames.isRefTree(refdb, ref.getName())) + txnHeads.add(ref.getObjectId()); else nonHeads.add(ref.getObjectId()); if (ref.getPeeledObjectId() != null) @@ -221,6 +222,7 @@ public boolean pack(ProgressMonitor pm) throws IOException { try { packHeads(pm); packRest(pm); + packRefTreeGraph(pm); packGarbage(pm); objdb.commitPack(newPackDesc, toPrune()); rollback = false; @@ -276,12 +278,11 @@ private void packHeads(ProgressMonitor pm) throws IOException { try (PackWriter pw = newPackWriter()) { pw.setTagTargets(tagTargets); - pw.preparePack(pm, allHeads, Collections. emptySet()); + pw.preparePack(pm, allHeads, none()); if (0 < pw.getObjectCount()) writePack(GC, pw, pm); } } - private void packRest(ProgressMonitor pm) throws IOException { if (nonHeads.isEmpty()) return; @@ -295,6 +296,23 @@ private void packRest(ProgressMonitor pm) throws IOException { } } + private void packRefTreeGraph(ProgressMonitor pm) throws IOException { + if (txnHeads.isEmpty()) + return; + + try (PackWriter pw = newPackWriter()) { + for (ObjectIdSet packedObjs : newPackObj) + pw.excludeObjects(packedObjs); + pw.preparePack(pm, txnHeads, none()); + if (0 < pw.getObjectCount()) + writePack(GC_TXN, pw, pm); + } + } + + private static Set none() { + return Collections. emptySet(); + } + private void packGarbage(ProgressMonitor pm) throws IOException { // TODO(sop) This is ugly. The garbage pack needs to be deleted. PackConfig cfg = new PackConfig(packConfig); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java index 5f491ff2f..3641560ee 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java @@ -90,6 +90,13 @@ public static enum PackSource { */ GC(1), + /** + * RefTreeGraph pack was created by Git garbage collection. + * + * @see DfsGarbageCollector + */ + GC_TXN(1), + /** * The pack was created by compacting multiple packs together. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index a5c95b3bc..8677164a6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -45,7 +45,6 @@ import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX; import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX; -import static org.eclipse.jgit.lib.RefDatabase.ALL; import java.io.File; import java.io.FileOutputStream; @@ -63,11 +62,9 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.TreeMap; @@ -80,6 +77,7 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.internal.storage.pack.PackWriter; +import org.eclipse.jgit.internal.storage.reftree.RefTreeNames; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; @@ -128,7 +126,7 @@ public class GC { * difference between the current refs and the refs which existed during * last {@link #repack()}. */ - private Map lastPackedRefs; + private Collection lastPackedRefs; /** * Holds the starting time of the last repack() execution. This is needed in @@ -362,17 +360,20 @@ public void prune(Set objectsToKeep) throws IOException, // during last repack(). Only those refs will survive which have been // added or modified since the last repack. Only these can save existing // loose refs from being pruned. - Map newRefs; + Collection newRefs; if (lastPackedRefs == null || lastPackedRefs.isEmpty()) newRefs = getAllRefs(); else { - newRefs = new HashMap(); - for (Iterator> i = getAllRefs().entrySet() - .iterator(); i.hasNext();) { - Entry newEntry = i.next(); - Ref old = lastPackedRefs.get(newEntry.getKey()); - if (!equals(newEntry.getValue(), old)) - newRefs.put(newEntry.getKey(), newEntry.getValue()); + Map last = new HashMap<>(); + for (Ref r : lastPackedRefs) { + last.put(r.getName(), r); + } + newRefs = new ArrayList<>(); + for (Ref r : getAllRefs()) { + Ref old = last.get(r.getName()); + if (!equals(r, old)) { + newRefs.add(r); + } } } @@ -384,10 +385,10 @@ public void prune(Set objectsToKeep) throws IOException, // leave this method. ObjectWalk w = new ObjectWalk(repo); try { - for (Ref cr : newRefs.values()) + for (Ref cr : newRefs) w.markStart(w.parseAny(cr.getObjectId())); if (lastPackedRefs != null) - for (Ref lpr : lastPackedRefs.values()) + for (Ref lpr : lastPackedRefs) w.markUninteresting(w.parseAny(lpr.getObjectId())); removeReferenced(deletionCandidates, w); } finally { @@ -405,11 +406,11 @@ public void prune(Set objectsToKeep) throws IOException, // additional reflog entries not handled during last repack() ObjectWalk w = new ObjectWalk(repo); try { - for (Ref ar : getAllRefs().values()) + for (Ref ar : getAllRefs()) for (ObjectId id : listRefLogObjects(ar, lastRepackTime)) w.markStart(w.parseAny(id)); if (lastPackedRefs != null) - for (Ref lpr : lastPackedRefs.values()) + for (Ref lpr : lastPackedRefs) w.markUninteresting(w.parseAny(lpr.getObjectId())); removeReferenced(deletionCandidates, w); } finally { @@ -530,19 +531,23 @@ public Collection repack() throws IOException { Collection toBeDeleted = repo.getObjectDatabase().getPacks(); long time = System.currentTimeMillis(); - Map refsBefore = getAllRefs(); + Collection refsBefore = getAllRefs(); Set allHeads = new HashSet(); Set nonHeads = new HashSet(); + Set txnHeads = new HashSet(); Set tagTargets = new HashSet(); Set indexObjects = listNonHEADIndexObjects(); + RefDatabase refdb = repo.getRefDatabase(); - for (Ref ref : refsBefore.values()) { + for (Ref ref : refsBefore) { nonHeads.addAll(listRefLogObjects(ref, 0)); if (ref.isSymbolic() || ref.getObjectId() == null) continue; if (ref.getName().startsWith(Constants.R_HEADS)) allHeads.add(ref.getObjectId()); + else if (RefTreeNames.isRefTree(refdb, ref.getName())) + txnHeads.add(ref.getObjectId()); else nonHeads.add(ref.getObjectId()); if (ref.getPeeledObjectId() != null) @@ -572,6 +577,11 @@ public Collection repack() throws IOException { if (rest != null) ret.add(rest); } + if (!txnHeads.isEmpty()) { + PackFile txn = writePack(txnHeads, null, null, excluded); + if (txn != null) + ret.add(txn); + } try { deleteOldPacks(toBeDeleted, ret); } catch (ParseException e) { @@ -624,11 +634,16 @@ private Set listRefLogObjects(Ref ref, long minTime) throws IOExceptio * @return a map where names of refs point to ref objects * @throws IOException */ - private Map getAllRefs() throws IOException { - Map ret = repo.getRefDatabase().getRefs(ALL); - for (Ref ref : repo.getRefDatabase().getAdditionalRefs()) - ret.put(ref.getName(), ref); - return ret; + private Collection getAllRefs() throws IOException { + Collection refs = RefTreeNames.allRefs(repo.getRefDatabase()); + List addl = repo.getRefDatabase().getAdditionalRefs(); + if (!addl.isEmpty()) { + List all = new ArrayList<>(refs.size() + addl.size()); + all.addAll(refs); + all.addAll(addl); + return all; + } + return refs; } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java new file mode 100644 index 000000000..239a74527 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2016, Google Inc. + * 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.internal.storage.reftree; + +import static org.eclipse.jgit.lib.RefDatabase.ALL; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefDatabase; + +/** Magic reference name logic for RefTrees. */ +public class RefTreeNames { + /** + * Suffix used on a {@link RefTreeDatabase#getTxnNamespace()} for user data. + *

+ * A {@link RefTreeDatabase}'s namespace may include a subspace (e.g. + * {@code "refs/txn/stage/"}) containing commit objects from the usual user + * portion of the repository (e.g. {@code "refs/heads/"}). These should be + * packed by the garbage collector alongside other user content rather than + * with the RefTree. + */ + private static final String STAGE = "stage/"; //$NON-NLS-1$ + + /** + * Determine if the reference is likely to be a RefTree. + * + * @param refdb + * database instance. + * @param ref + * reference name. + * @return {@code true} if the reference is a RefTree. + */ + public static boolean isRefTree(RefDatabase refdb, String ref) { + if (refdb instanceof RefTreeDatabase) { + RefTreeDatabase b = (RefTreeDatabase) refdb; + if (ref.equals(b.getTxnCommitted())) { + return true; + } + + String namespace = b.getTxnNamespace(); + if (namespace != null + && ref.startsWith(namespace) + && !ref.startsWith(namespace + STAGE)) { + return true; + } + } + return false; + } + + /** + * Snapshot all references from a RefTreeDatabase and its bootstrap. + *

+ * There may be name conflicts with multiple {@link Ref} objects containing + * the same name in the returned collection. + * + * @param refdb + * database instance. + * @return all known references. + * @throws IOException + * references cannot be enumerated. + */ + public static Collection allRefs(RefDatabase refdb) + throws IOException { + Collection refs = refdb.getRefs(ALL).values(); + if (!(refdb instanceof RefTreeDatabase)) { + return refs; + } + + RefDatabase bootstrap = ((RefTreeDatabase) refdb).getBootstrap(); + Collection br = bootstrap.getRefs(ALL).values(); + List all = new ArrayList<>(refs.size() + br.size()); + all.addAll(refs); + all.addAll(br); + return all; + } + + private RefTreeNames() { + } +} From 4e650c0d76b716c0e9cb3592d30def9e609066c1 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Tue, 12 Jan 2016 16:11:36 -0800 Subject: [PATCH 78/89] PackWriter: Declare preparePack object sets as @NonNull Require callers to pass in valid sets for both want and have collections. Offer PackWriter.NONE as a handy constant for an empty collection for the have part of preparePack instead of null. Change-Id: Ifda4450f5e488cbfefd728382b7d30797e229217 --- .../eclipse/jgit/junit/TestRepository.java | 2 +- .../internal/storage/file/PackWriterTest.java | 23 +++++------ .../storage/dfs/DfsGarbageCollector.java | 9 +---- .../jgit/internal/storage/file/GC.java | 7 ++-- .../internal/storage/pack/PackWriter.java | 39 ++++++++++--------- 5 files changed, 38 insertions(+), 42 deletions(-) diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index c941afc78..3a4f9c788 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -866,7 +866,7 @@ public void packAndPrune() throws Exception { Set all = new HashSet(); for (Ref r : db.getAllRefs().values()) all.add(r.getObjectId()); - pw.preparePack(m, all, Collections. emptySet()); + pw.preparePack(m, all, PackWriter.NONE); final ObjectId name = pw.computeName(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java index 3c44799b6..01d6ee68e 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java @@ -49,6 +49,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -88,9 +89,6 @@ public class PackWriterTest extends SampleDataRepositoryTestCase { - private static final Set EMPTY_SET_OBJECT = Collections - . emptySet(); - private static final List EMPTY_LIST_REVS = Collections . emptyList(); @@ -171,7 +169,7 @@ public void testModifySettings() { */ @Test public void testWriteEmptyPack1() throws IOException { - createVerifyOpenPack(EMPTY_SET_OBJECT, EMPTY_SET_OBJECT, false, false); + createVerifyOpenPack(NONE, NONE, false, false); assertEquals(0, writer.getObjectCount()); assertEquals(0, pack.getObjectCount()); @@ -204,8 +202,8 @@ public void testNotIgnoreNonExistingObjects() throws IOException { final ObjectId nonExisting = ObjectId .fromString("0000000000000000000000000000000000000001"); try { - createVerifyOpenPack(EMPTY_SET_OBJECT, Collections.singleton( - nonExisting), false, false); + createVerifyOpenPack(NONE, Collections.singleton(nonExisting), + false, false); fail("Should have thrown MissingObjectException"); } catch (MissingObjectException x) { // expected @@ -221,8 +219,8 @@ public void testNotIgnoreNonExistingObjects() throws IOException { public void testIgnoreNonExistingObjects() throws IOException { final ObjectId nonExisting = ObjectId .fromString("0000000000000000000000000000000000000001"); - createVerifyOpenPack(EMPTY_SET_OBJECT, Collections.singleton( - nonExisting), false, true); + createVerifyOpenPack(NONE, Collections.singleton(nonExisting), + false, true); // shouldn't throw anything } @@ -240,8 +238,8 @@ public void testIgnoreNonExistingObjectsWithBitmaps() throws IOException, final ObjectId nonExisting = ObjectId .fromString("0000000000000000000000000000000000000001"); new GC(db).gc(); - createVerifyOpenPack(EMPTY_SET_OBJECT, - Collections.singleton(nonExisting), false, true, true); + createVerifyOpenPack(NONE, Collections.singleton(nonExisting), false, + true, true); // shouldn't throw anything } @@ -552,8 +550,7 @@ private static PackIndex writePack(FileRepository repo, pw.setReuseDeltaCommits(false); for (ObjectIdSet idx : excludeObjects) pw.excludeObjects(idx); - pw.preparePack(NullProgressMonitor.INSTANCE, want, - Collections. emptySet()); + pw.preparePack(NullProgressMonitor.INSTANCE, want, NONE); String id = pw.computeName().getName(); File packdir = new File(repo.getObjectsDirectory(), "pack"); File packFile = new File(packdir, "pack-" + id + ".pack"); @@ -576,7 +573,7 @@ private void writeVerifyPack1() throws IOException { final HashSet interestings = new HashSet(); interestings.add(ObjectId .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); - createVerifyOpenPack(interestings, EMPTY_SET_OBJECT, false, false); + createVerifyOpenPack(interestings, NONE, false, false); final ObjectId expectedOrder[] = new ObjectId[] { ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java index c48a49da7..784507d88 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java @@ -53,7 +53,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -278,7 +277,7 @@ private void packHeads(ProgressMonitor pm) throws IOException { try (PackWriter pw = newPackWriter()) { pw.setTagTargets(tagTargets); - pw.preparePack(pm, allHeads, none()); + pw.preparePack(pm, allHeads, PackWriter.NONE); if (0 < pw.getObjectCount()) writePack(GC, pw, pm); } @@ -303,16 +302,12 @@ private void packRefTreeGraph(ProgressMonitor pm) throws IOException { try (PackWriter pw = newPackWriter()) { for (ObjectIdSet packedObjs : newPackObj) pw.excludeObjects(packedObjs); - pw.preparePack(pm, txnHeads, none()); + pw.preparePack(pm, txnHeads, PackWriter.NONE); if (0 < pw.getObjectCount()) writePack(GC_TXN, pw, pm); } } - private static Set none() { - return Collections. emptySet(); - } - private void packGarbage(ProgressMonitor pm) throws IOException { // TODO(sop) This is ugly. The garbage pack needs to be deleted. PackConfig cfg = new PackConfig(packConfig); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index 8677164a6..2ce0d4734 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -69,6 +69,7 @@ import java.util.Set; import java.util.TreeMap; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; @@ -578,7 +579,7 @@ else if (RefTreeNames.isRefTree(refdb, ref.getName())) ret.add(rest); } if (!txnHeads.isEmpty()) { - PackFile txn = writePack(txnHeads, null, null, excluded); + PackFile txn = writePack(txnHeads, PackWriter.NONE, null, excluded); if (txn != null) ret.add(txn); } @@ -698,8 +699,8 @@ private Set listNonHEADIndexObjects() } } - private PackFile writePack(Set want, - Set have, Set tagTargets, + private PackFile writePack(@NonNull Set want, + @NonNull Set have, Set tagTargets, List excludeObjects) throws IOException { File tmpPack = null; Map tmpExts = new TreeMap( diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java index 763f35c21..525f9aecc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java @@ -80,6 +80,7 @@ import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.LargeObjectException; @@ -162,6 +163,9 @@ public class PackWriter implements AutoCloseable { private static final int PACK_VERSION_GENERATED = 2; + /** Empty set of objects for {@code preparePack()}. */ + public static Set NONE = Collections.emptySet(); + private static final Map, Boolean> instances = new ConcurrentHashMap, Boolean>(); @@ -670,7 +674,7 @@ public void excludeObjects(ObjectIdSet idx) { * @throws IOException * when some I/O problem occur during reading objects. */ - public void preparePack(final Iterator objectsSource) + public void preparePack(@NonNull Iterator objectsSource) throws IOException { while (objectsSource.hasNext()) { addObject(objectsSource.next()); @@ -693,16 +697,18 @@ public void preparePack(final Iterator objectsSource) * progress during object enumeration. * @param want * collection of objects to be marked as interesting (start - * points of graph traversal). + * points of graph traversal). Must not be {@code null}. * @param have * collection of objects to be marked as uninteresting (end - * points of graph traversal). + * points of graph traversal). Pass {@link #NONE} if all objects + * reachable from {@code want} are desired, such as when serving + * a clone. * @throws IOException * when some I/O problem occur during reading objects. */ public void preparePack(ProgressMonitor countingMonitor, - Set want, - Set have) throws IOException { + @NonNull Set want, + @NonNull Set have) throws IOException { ObjectWalk ow; if (shallowPack) ow = new DepthWalk.ObjectWalk(reader, depth); @@ -729,17 +735,19 @@ public void preparePack(ProgressMonitor countingMonitor, * ObjectWalk to perform enumeration. * @param interestingObjects * collection of objects to be marked as interesting (start - * points of graph traversal). + * points of graph traversal). Must not be {@code null}. * @param uninterestingObjects * collection of objects to be marked as uninteresting (end - * points of graph traversal). + * points of graph traversal). Pass {@link #NONE} if all objects + * reachable from {@code want} are desired, such as when serving + * a clone. * @throws IOException * when some I/O problem occur during reading objects. */ public void preparePack(ProgressMonitor countingMonitor, - ObjectWalk walk, - final Set interestingObjects, - final Set uninterestingObjects) + @NonNull ObjectWalk walk, + @NonNull Set interestingObjects, + @NonNull Set uninterestingObjects) throws IOException { if (countingMonitor == null) countingMonitor = NullProgressMonitor.INSTANCE; @@ -1597,17 +1605,12 @@ private void writeChecksum(PackOutputStream out) throws IOException { out.write(packcsum); } - private void findObjectsToPack(final ProgressMonitor countingMonitor, - final ObjectWalk walker, final Set want, - Set have) - throws MissingObjectException, IOException, - IncorrectObjectTypeException { + private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor, + @NonNull ObjectWalk walker, @NonNull Set want, + @NonNull Set have) throws IOException { final long countingStart = System.currentTimeMillis(); beginPhase(PackingPhase.COUNTING, countingMonitor, ProgressMonitor.UNKNOWN); - if (have == null) - have = Collections.emptySet(); - stats.interestingObjects = Collections.unmodifiableSet(new HashSet(want)); stats.uninterestingObjects = Collections.unmodifiableSet(new HashSet(have)); From 2cc80187d3633adedc99eb97132e0a749b457c19 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Fri, 15 Jan 2016 10:45:36 -0500 Subject: [PATCH 79/89] Revert "Remove deprecated Tree, TreeEntry, FileTreeEntry and friends" This reverts commit 0f8743d4d7a4f3af1eccea60d45d51d13f1a2ad4. JGit is unable to iterate its API. Change-Id: Ie3d6a28e622a5c0cf54768a2299f1c44c0114c19 --- .../storage/file/T0003_BasicTest.java | 53 +- .../org/eclipse/jgit/lib/IndexDiffTest.java | 89 +-- .../org/eclipse/jgit/lib/T0002_TreeTest.java | 319 ++++++++++ .../eclipse/jgit/revwalk/ObjectWalkTest.java | 39 +- .../jgit/treewalk/TreeWalkBasicDiffTest.java | 77 +-- .../org/eclipse/jgit/lib/FileTreeEntry.java | 115 ++++ .../eclipse/jgit/lib/GitlinkTreeEntry.java | 87 +++ .../eclipse/jgit/lib/SymlinkTreeEntry.java | 85 +++ .../src/org/eclipse/jgit/lib/Tree.java | 601 ++++++++++++++++++ .../src/org/eclipse/jgit/lib/TreeEntry.java | 256 ++++++++ 10 files changed, 1614 insertions(+), 107 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java index a100ff357..5670a9634 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java @@ -67,6 +67,7 @@ import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.FileTreeEntry; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; @@ -74,6 +75,7 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.TagBuilder; +import org.eclipse.jgit.lib.Tree; import org.eclipse.jgit.lib.TreeFormatter; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTag; @@ -416,6 +418,29 @@ public void test009_CreateCommitOldFormat() throws IOException { assertEquals(c.getCommitter(), c2.getCommitterIdent()); } + @Test + public void test012_SubtreeExternalSorting() throws IOException { + final ObjectId emptyBlob = insertEmptyBlob(); + final Tree t = new Tree(db); + final FileTreeEntry e0 = t.addFile("a-"); + final FileTreeEntry e1 = t.addFile("a-b"); + final FileTreeEntry e2 = t.addFile("a/b"); + final FileTreeEntry e3 = t.addFile("a="); + final FileTreeEntry e4 = t.addFile("a=b"); + + e0.setId(emptyBlob); + e1.setId(emptyBlob); + e2.setId(emptyBlob); + e3.setId(emptyBlob); + e4.setId(emptyBlob); + + final Tree a = (Tree) t.findTreeMember("a"); + a.setId(insertTree(a)); + assertEquals(ObjectId + .fromString("b47a8f0a4190f7572e11212769090523e23eb1ea"), + insertTree(t)); + } + @Test public void test020_createBlobTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); @@ -439,8 +464,9 @@ public void test020_createBlobTag() throws IOException { @Test public void test021_createTreeTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - TreeFormatter almostEmptyTree = new TreeFormatter(); - almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, + "empty".getBytes(), false)); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); final TagBuilder t = new TagBuilder(); t.setObjectId(almostEmptyTreeId, Constants.OBJ_TREE); @@ -462,8 +488,9 @@ public void test021_createTreeTag() throws IOException { @Test public void test022_createCommitTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - TreeFormatter almostEmptyTree = new TreeFormatter(); - almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, + "empty".getBytes(), false)); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); final CommitBuilder almostEmptyCommit = new CommitBuilder(); almostEmptyCommit.setAuthor(new PersonIdent(author, 1154236443000L, @@ -493,8 +520,9 @@ public void test022_createCommitTag() throws IOException { @Test public void test023_createCommitNonAnullii() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - TreeFormatter almostEmptyTree = new TreeFormatter(); - almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, + "empty".getBytes(), false)); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); CommitBuilder commit = new CommitBuilder(); commit.setTreeId(almostEmptyTreeId); @@ -514,8 +542,9 @@ public void test023_createCommitNonAnullii() throws IOException { @Test public void test024_createCommitNonAscii() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - TreeFormatter almostEmptyTree = new TreeFormatter(); - almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, + "empty".getBytes(), false)); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); CommitBuilder commit = new CommitBuilder(); commit.setTreeId(almostEmptyTreeId); @@ -717,6 +746,14 @@ private ObjectId insertEmptyBlob() throws IOException { return emptyId; } + private ObjectId insertTree(Tree tree) throws IOException { + try (ObjectInserter oi = db.newObjectInserter()) { + ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format()); + oi.flush(); + return id; + } + } + private ObjectId insertTree(TreeFormatter tree) throws IOException { try (ObjectInserter oi = db.newObjectInserter()) { ObjectId id = oi.insert(tree); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java index 7fcee3dc8..a5cd7b5c0 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java @@ -99,7 +99,8 @@ public void apply(DirCacheEntry ent) { public void testAdded() throws IOException { writeTrashFile("file1", "file1"); writeTrashFile("dir/subfile", "dir/subfile"); - ObjectId tree = insertTree(new TreeFormatter()); + Tree tree = new Tree(db); + tree.setId(insertTree(tree)); DirCache index = db.lockDirCache(); DirCacheEditor editor = index.editor(); @@ -107,7 +108,7 @@ public void testAdded() throws IOException { editor.add(add(db, trash, "dir/subfile")); editor.commit(); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree, iterator); + IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); diff.diff(); assertEquals(2, diff.getAdded().size()); assertTrue(diff.getAdded().contains("file1")); @@ -123,16 +124,18 @@ public void testRemoved() throws IOException { writeTrashFile("file2", "file2"); writeTrashFile("dir/file3", "dir/file3"); - TreeFormatter dir = new TreeFormatter(); - dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b")); - - TreeFormatter tree = new TreeFormatter(); - tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad")); - tree.append("dir", FileMode.TREE, insertTree(dir)); - ObjectId treeId = insertTree(tree); + Tree tree = new Tree(db); + tree.addFile("file2"); + tree.addFile("dir/file3"); + assertEquals(2, tree.memberCount()); + tree.findBlobMember("file2").setId(ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad")); + Tree tree2 = (Tree) tree.findTreeMember("dir"); + tree2.findBlobMember("file3").setId(ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b")); + tree2.setId(insertTree(tree2)); + tree.setId(insertTree(tree)); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, treeId, iterator); + IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); diff.diff(); assertEquals(2, diff.getRemoved().size()); assertTrue(diff.getRemoved().contains("file2")); @@ -154,16 +157,16 @@ public void testModified() throws IOException, GitAPIException { writeTrashFile("dir/file3", "changed"); - TreeFormatter dir = new TreeFormatter(); - dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789")); - - TreeFormatter tree = new TreeFormatter(); - tree.append("dir", FileMode.TREE, insertTree(dir)); - tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789")); - ObjectId treeId = insertTree(tree); + Tree tree = new Tree(db); + tree.addFile("file2").setId(ObjectId.fromString("0123456789012345678901234567890123456789")); + tree.addFile("dir/file3").setId(ObjectId.fromString("0123456789012345678901234567890123456789")); + assertEquals(2, tree.memberCount()); + Tree tree2 = (Tree) tree.findTreeMember("dir"); + tree2.setId(insertTree(tree2)); + tree.setId(insertTree(tree)); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, treeId, iterator); + IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); diff.diff(); assertEquals(2, diff.getChanged().size()); assertTrue(diff.getChanged().contains("file2")); @@ -311,16 +314,17 @@ public void testUnchangedSimple() throws IOException, GitAPIException { git.add().addFilepattern("a=c").call(); git.add().addFilepattern("a=d").call(); - TreeFormatter tree = new TreeFormatter(); + Tree tree = new Tree(db); // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin - tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); - tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); - tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); - tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); - ObjectId treeId = insertTree(tree); + tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); + tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); + tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); + tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); + + tree.setId(insertTree(tree)); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, treeId, iterator); + IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); diff.diff(); assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getAdded().size()); @@ -352,27 +356,24 @@ public void testUnchangedComplex() throws IOException, GitAPIException { .addFilepattern("a/c").addFilepattern("a=c") .addFilepattern("a=d").call(); - + Tree tree = new Tree(db); // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin - TreeFormatter bb = new TreeFormatter(); - bb.append("b", FileMode.REGULAR_FILE, ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd")); + tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); + tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); + tree.addFile("a/b.b/b").setId(ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd")); + tree.addFile("a/b").setId(ObjectId.fromString("db89c972fc57862eae378f45b74aca228037d415")); + tree.addFile("a/c").setId(ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007")); + tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); + tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); - TreeFormatter a = new TreeFormatter(); - a.append("b", FileMode.REGULAR_FILE, ObjectId - .fromString("db89c972fc57862eae378f45b74aca228037d415")); - a.append("b.b", FileMode.TREE, insertTree(bb)); - a.append("c", FileMode.REGULAR_FILE, ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007")); - - TreeFormatter tree = new TreeFormatter(); - tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); - tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); - tree.append("a", FileMode.TREE, insertTree(a)); - tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); - tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); - ObjectId treeId = insertTree(tree); + Tree tree3 = (Tree) tree.findTreeMember("a/b.b"); + tree3.setId(insertTree(tree3)); + Tree tree2 = (Tree) tree.findTreeMember("a"); + tree2.setId(insertTree(tree2)); + tree.setId(insertTree(tree)); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, treeId, iterator); + IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); diff.diff(); assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getAdded().size()); @@ -382,9 +383,9 @@ public void testUnchangedComplex() throws IOException, GitAPIException { assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } - private ObjectId insertTree(TreeFormatter tree) throws IOException { + private ObjectId insertTree(Tree tree) throws IOException { try (ObjectInserter oi = db.newObjectInserter()) { - ObjectId id = oi.insert(tree); + ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format()); oi.flush(); return id; } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java new file mode 100644 index 000000000..651e62c9c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; +import org.junit.Test; + +@SuppressWarnings("deprecation") +public class T0002_TreeTest extends SampleDataRepositoryTestCase { + private static final ObjectId SOME_FAKE_ID = ObjectId.fromString( + "0123456789abcdef0123456789abcdef01234567"); + + private static int compareNamesUsingSpecialCompare(String a, String b) + throws UnsupportedEncodingException { + char lasta = '\0'; + byte[] abytes; + if (a.length() > 0 && a.charAt(a.length()-1) == '/') { + lasta = '/'; + a = a.substring(0, a.length() - 1); + } + abytes = a.getBytes("ISO-8859-1"); + char lastb = '\0'; + byte[] bbytes; + if (b.length() > 0 && b.charAt(b.length()-1) == '/') { + lastb = '/'; + b = b.substring(0, b.length() - 1); + } + bbytes = b.getBytes("ISO-8859-1"); + return Tree.compareNames(abytes, bbytes, lasta, lastb); + } + + @Test + public void test000_sort_01() throws UnsupportedEncodingException { + assertEquals(0, compareNamesUsingSpecialCompare("a","a")); + } + + @Test + public void test000_sort_02() throws UnsupportedEncodingException { + assertEquals(-1, compareNamesUsingSpecialCompare("a","b")); + assertEquals(1, compareNamesUsingSpecialCompare("b","a")); + } + + @Test + public void test000_sort_03() throws UnsupportedEncodingException { + assertEquals(1, compareNamesUsingSpecialCompare("a:","a")); + assertEquals(1, compareNamesUsingSpecialCompare("a/","a")); + assertEquals(-1, compareNamesUsingSpecialCompare("a","a/")); + assertEquals(-1, compareNamesUsingSpecialCompare("a","a:")); + assertEquals(1, compareNamesUsingSpecialCompare("a:","a/")); + assertEquals(-1, compareNamesUsingSpecialCompare("a/","a:")); + } + + @Test + public void test000_sort_04() throws UnsupportedEncodingException { + assertEquals(-1, compareNamesUsingSpecialCompare("a.a","a/a")); + assertEquals(1, compareNamesUsingSpecialCompare("a/a","a.a")); + } + + @Test + public void test000_sort_05() throws UnsupportedEncodingException { + assertEquals(-1, compareNamesUsingSpecialCompare("a.","a/")); + assertEquals(1, compareNamesUsingSpecialCompare("a/","a.")); + + } + + @Test + public void test001_createEmpty() throws IOException { + final Tree t = new Tree(db); + assertTrue("isLoaded", t.isLoaded()); + assertTrue("isModified", t.isModified()); + assertTrue("no parent", t.getParent() == null); + assertTrue("isRoot", t.isRoot()); + assertTrue("no name", t.getName() == null); + assertTrue("no nameUTF8", t.getNameUTF8() == null); + assertTrue("has entries array", t.members() != null); + assertEquals("entries is empty", 0, t.members().length); + assertEquals("full name is empty", "", t.getFullName()); + assertTrue("no id", t.getId() == null); + assertTrue("database is r", t.getRepository() == db); + assertTrue("no foo child", t.findTreeMember("foo") == null); + assertTrue("no foo child", t.findBlobMember("foo") == null); + } + + @Test + public void test002_addFile() throws IOException { + final Tree t = new Tree(db); + t.setId(SOME_FAKE_ID); + assertTrue("has id", t.getId() != null); + assertFalse("not modified", t.isModified()); + + final String n = "bob"; + final FileTreeEntry f = t.addFile(n); + assertNotNull("have file", f); + assertEquals("name matches", n, f.getName()); + assertEquals("name matches", f.getName(), new String(f.getNameUTF8(), + "UTF-8")); + assertEquals("full name matches", n, f.getFullName()); + assertTrue("no id", f.getId() == null); + assertTrue("is modified", t.isModified()); + assertTrue("has no id", t.getId() == null); + assertTrue("found bob", t.findBlobMember(f.getName()) == f); + + final TreeEntry[] i = t.members(); + assertNotNull("members array not null", i); + assertTrue("iterator is not empty", i != null && i.length > 0); + assertTrue("iterator returns file", i != null && i[0] == f); + assertTrue("iterator is empty", i != null && i.length == 1); + } + + @Test + public void test004_addTree() throws IOException { + final Tree t = new Tree(db); + t.setId(SOME_FAKE_ID); + assertTrue("has id", t.getId() != null); + assertFalse("not modified", t.isModified()); + + final String n = "bob"; + final Tree f = t.addTree(n); + assertNotNull("have tree", f); + assertEquals("name matches", n, f.getName()); + assertEquals("name matches", f.getName(), new String(f.getNameUTF8(), + "UTF-8")); + assertEquals("full name matches", n, f.getFullName()); + assertTrue("no id", f.getId() == null); + assertTrue("parent matches", f.getParent() == t); + assertTrue("repository matches", f.getRepository() == db); + assertTrue("isLoaded", f.isLoaded()); + assertFalse("has items", f.members().length > 0); + assertFalse("is root", f.isRoot()); + assertTrue("parent is modified", t.isModified()); + assertTrue("parent has no id", t.getId() == null); + assertTrue("found bob child", t.findTreeMember(f.getName()) == f); + + final TreeEntry[] i = t.members(); + assertTrue("iterator is not empty", i.length > 0); + assertTrue("iterator returns file", i[0] == f); + assertEquals("iterator is empty", 1, i.length); + } + + @Test + public void test005_addRecursiveFile() throws IOException { + final Tree t = new Tree(db); + final FileTreeEntry f = t.addFile("a/b/c"); + assertNotNull("created f", f); + assertEquals("c", f.getName()); + assertEquals("b", f.getParent().getName()); + assertEquals("a", f.getParent().getParent().getName()); + assertTrue("t is great-grandparent", t == f.getParent().getParent() + .getParent()); + } + + @Test + public void test005_addRecursiveTree() throws IOException { + final Tree t = new Tree(db); + final Tree f = t.addTree("a/b/c"); + assertNotNull("created f", f); + assertEquals("c", f.getName()); + assertEquals("b", f.getParent().getName()); + assertEquals("a", f.getParent().getParent().getName()); + assertTrue("t is great-grandparent", t == f.getParent().getParent() + .getParent()); + } + + @Test + public void test006_addDeepTree() throws IOException { + final Tree t = new Tree(db); + + final Tree e = t.addTree("e"); + assertNotNull("have e", e); + assertTrue("e.parent == t", e.getParent() == t); + final Tree f = t.addTree("f"); + assertNotNull("have f", f); + assertTrue("f.parent == t", f.getParent() == t); + final Tree g = f.addTree("g"); + assertNotNull("have g", g); + assertTrue("g.parent == f", g.getParent() == f); + final Tree h = g.addTree("h"); + assertNotNull("have h", h); + assertTrue("h.parent = g", h.getParent() == g); + + h.setId(SOME_FAKE_ID); + assertTrue("h not modified", !h.isModified()); + g.setId(SOME_FAKE_ID); + assertTrue("g not modified", !g.isModified()); + f.setId(SOME_FAKE_ID); + assertTrue("f not modified", !f.isModified()); + e.setId(SOME_FAKE_ID); + assertTrue("e not modified", !e.isModified()); + t.setId(SOME_FAKE_ID); + assertTrue("t not modified.", !t.isModified()); + + assertEquals("full path of h ok", "f/g/h", h.getFullName()); + assertTrue("Can find h", t.findTreeMember(h.getFullName()) == h); + assertTrue("Can't find f/z", t.findBlobMember("f/z") == null); + assertTrue("Can't find y/z", t.findBlobMember("y/z") == null); + + final FileTreeEntry i = h.addFile("i"); + assertNotNull(i); + assertEquals("full path of i ok", "f/g/h/i", i.getFullName()); + assertTrue("Can find i", t.findBlobMember(i.getFullName()) == i); + assertTrue("h modified", h.isModified()); + assertTrue("g modified", g.isModified()); + assertTrue("f modified", f.isModified()); + assertTrue("e not modified", !e.isModified()); + assertTrue("t modified", t.isModified()); + + assertTrue("h no id", h.getId() == null); + assertTrue("g no id", g.getId() == null); + assertTrue("f no id", f.getId() == null); + assertTrue("e has id", e.getId() != null); + assertTrue("t no id", t.getId() == null); + } + + @Test + public void test007_manyFileLookup() throws IOException { + final Tree t = new Tree(db); + final List files = new ArrayList(26 * 26); + for (char level1 = 'a'; level1 <= 'z'; level1++) { + for (char level2 = 'a'; level2 <= 'z'; level2++) { + final String n = "." + level1 + level2 + "9"; + final FileTreeEntry f = t.addFile(n); + assertNotNull("File " + n + " added.", f); + assertEquals(n, f.getName()); + files.add(f); + } + } + assertEquals(files.size(), t.memberCount()); + final TreeEntry[] ents = t.members(); + assertNotNull(ents); + assertEquals(files.size(), ents.length); + for (int k = 0; k < ents.length; k++) { + assertTrue("File " + files.get(k).getName() + + " is at " + k + ".", files.get(k) == ents[k]); + } + } + + @Test + public void test008_SubtreeInternalSorting() throws IOException { + final Tree t = new Tree(db); + final FileTreeEntry e0 = t.addFile("a-b"); + final FileTreeEntry e1 = t.addFile("a-"); + final FileTreeEntry e2 = t.addFile("a=b"); + final Tree e3 = t.addTree("a"); + final FileTreeEntry e4 = t.addFile("a="); + + final TreeEntry[] ents = t.members(); + assertSame(e1, ents[0]); + assertSame(e0, ents[1]); + assertSame(e3, ents[2]); + assertSame(e4, ents[3]); + assertSame(e2, ents[4]); + } + + @Test + public void test009_SymlinkAndGitlink() throws IOException { + final Tree symlinkTree = mapTree("symlink"); + assertTrue("Symlink entry exists", symlinkTree.existsBlob("symlink.txt")); + final Tree gitlinkTree = mapTree("gitlink"); + assertTrue("Gitlink entry exists", gitlinkTree.existsBlob("submodule")); + } + + private Tree mapTree(String name) throws IOException { + ObjectId id = db.resolve(name + "^{tree}"); + return new Tree(db, id, db.open(id).getCachedBytes()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java index 9c9edc147..2a59f58c6 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java @@ -47,10 +47,11 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileTreeEntry; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.TreeFormatter; +import org.eclipse.jgit.lib.Tree; import org.junit.Test; @SuppressWarnings("deprecation") @@ -219,24 +220,28 @@ public void testEmptyTreeCorruption() throws Exception { .fromString("abbbfafe3129f85747aba7bfac992af77134c607"); final RevTree tree_root, tree_A, tree_AB; final RevCommit b; - try (ObjectInserter inserter = db.newObjectInserter()) { - ObjectId empty = inserter.insert(new TreeFormatter()); + { + Tree root = new Tree(db); + Tree A = root.addTree("A"); + FileTreeEntry B = root.addFile("B"); + B.setId(bId); - TreeFormatter A = new TreeFormatter(); - A.append("A", FileMode.TREE, empty); - A.append("B", FileMode.TREE, empty); - ObjectId idA = inserter.insert(A); + Tree A_A = A.addTree("A"); + Tree A_B = A.addTree("B"); - TreeFormatter root = new TreeFormatter(); - root.append("A", FileMode.TREE, idA); - root.append("B", FileMode.REGULAR_FILE, bId); - ObjectId idRoot = inserter.insert(root); - inserter.flush(); + try (final ObjectInserter inserter = db.newObjectInserter()) { + A_A.setId(inserter.insert(Constants.OBJ_TREE, A_A.format())); + A_B.setId(inserter.insert(Constants.OBJ_TREE, A_B.format())); + A.setId(inserter.insert(Constants.OBJ_TREE, A.format())); + root.setId(inserter.insert(Constants.OBJ_TREE, root.format())); + inserter.flush(); + } - tree_root = objw.parseTree(idRoot); - tree_A = objw.parseTree(idA); - tree_AB = objw.parseTree(empty); - b = commit(tree_root); + tree_root = rw.parseTree(root.getId()); + tree_A = rw.parseTree(A.getId()); + tree_AB = rw.parseTree(A_A.getId()); + assertSame(tree_AB, rw.parseTree(A_B.getId())); + b = commit(rw.parseTree(root.getId())); } markStart(b); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java index c3ff7df8f..aca7c80fd 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.treewalk; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; import static org.eclipse.jgit.lib.Constants.encode; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -53,10 +54,11 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.TreeFormatter; +import org.eclipse.jgit.lib.Tree; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.junit.Test; +@SuppressWarnings("deprecation") public class TreeWalkBasicDiffTest extends RepositoryTestCase { @Test public void testMissingSubtree_DetectFileAdded_FileModified() @@ -70,63 +72,62 @@ public void testMissingSubtree_DetectFileAdded_FileModified() // Create sub-a/empty, sub-c/empty = hello. { - TreeFormatter root = new TreeFormatter(); + final Tree root = new Tree(db); { - TreeFormatter subA = new TreeFormatter(); - subA.append("empty", FileMode.REGULAR_FILE, aFileId); - root.append("sub-a", FileMode.TREE, inserter.insert(subA)); + final Tree subA = root.addTree("sub-a"); + subA.addFile("empty").setId(aFileId); + subA.setId(inserter.insert(OBJ_TREE, subA.format())); } { - TreeFormatter subC = new TreeFormatter(); - subC.append("empty", FileMode.REGULAR_FILE, cFileId1); - root.append("sub-c", FileMode.TREE, inserter.insert(subC)); + final Tree subC = root.addTree("sub-c"); + subC.addFile("empty").setId(cFileId1); + subC.setId(inserter.insert(OBJ_TREE, subC.format())); } - oldTree = inserter.insert(root); + oldTree = inserter.insert(OBJ_TREE, root.format()); } // Create sub-a/empty, sub-b/empty, sub-c/empty. { - TreeFormatter root = new TreeFormatter(); + final Tree root = new Tree(db); { - TreeFormatter subA = new TreeFormatter(); - subA.append("empty", FileMode.REGULAR_FILE, aFileId); - root.append("sub-a", FileMode.TREE, inserter.insert(subA)); + final Tree subA = root.addTree("sub-a"); + subA.addFile("empty").setId(aFileId); + subA.setId(inserter.insert(OBJ_TREE, subA.format())); } { - TreeFormatter subB = new TreeFormatter(); - subB.append("empty", FileMode.REGULAR_FILE, bFileId); - root.append("sub-b", FileMode.TREE, inserter.insert(subB)); + final Tree subB = root.addTree("sub-b"); + subB.addFile("empty").setId(bFileId); + subB.setId(inserter.insert(OBJ_TREE, subB.format())); } { - TreeFormatter subC = new TreeFormatter(); - subC.append("empty", FileMode.REGULAR_FILE, cFileId2); - root.append("sub-c", FileMode.TREE, inserter.insert(subC)); + final Tree subC = root.addTree("sub-c"); + subC.addFile("empty").setId(cFileId2); + subC.setId(inserter.insert(OBJ_TREE, subC.format())); } - newTree = inserter.insert(root); + newTree = inserter.insert(OBJ_TREE, root.format()); } inserter.flush(); } - try (TreeWalk tw = new TreeWalk(db)) { - tw.reset(oldTree, newTree); - tw.setRecursive(true); - tw.setFilter(TreeFilter.ANY_DIFF); + final TreeWalk tw = new TreeWalk(db); + tw.reset(oldTree, newTree); + tw.setRecursive(true); + tw.setFilter(TreeFilter.ANY_DIFF); - assertTrue(tw.next()); - assertEquals("sub-b/empty", tw.getPathString()); - assertEquals(FileMode.MISSING, tw.getFileMode(0)); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); - assertEquals(ObjectId.zeroId(), tw.getObjectId(0)); - assertEquals(bFileId, tw.getObjectId(1)); + assertTrue(tw.next()); + assertEquals("sub-b/empty", tw.getPathString()); + assertEquals(FileMode.MISSING, tw.getFileMode(0)); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); + assertEquals(ObjectId.zeroId(), tw.getObjectId(0)); + assertEquals(bFileId, tw.getObjectId(1)); - assertTrue(tw.next()); - assertEquals("sub-c/empty", tw.getPathString()); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0)); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); - assertEquals(cFileId1, tw.getObjectId(0)); - assertEquals(cFileId2, tw.getObjectId(1)); + assertTrue(tw.next()); + assertEquals("sub-c/empty", tw.getPathString()); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0)); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); + assertEquals(cFileId1, tw.getObjectId(0)); + assertEquals(cFileId2, tw.getObjectId(1)); - assertFalse(tw.next()); - } + assertFalse(tw.next()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java new file mode 100644 index 000000000..6811417ee --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import java.io.IOException; + +/** + * A representation of a file (blob) object in a {@link Tree}. + * + * @deprecated To look up information about a single path, use + * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. + * To lookup information about multiple paths at once, use a + * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's + * information from its getter methods. + */ +@Deprecated +public class FileTreeEntry extends TreeEntry { + private FileMode mode; + + /** + * Constructor for a File (blob) object. + * + * @param parent + * The {@link Tree} holding this object (or null) + * @param id + * the SHA-1 of the blob (or null for a yet unhashed file) + * @param nameUTF8 + * raw object name in the parent tree + * @param execute + * true if the executable flag is set + */ + public FileTreeEntry(final Tree parent, final ObjectId id, + final byte[] nameUTF8, final boolean execute) { + super(parent, id, nameUTF8); + setExecutable(execute); + } + + public FileMode getMode() { + return mode; + } + + /** + * @return true if this file is executable + */ + public boolean isExecutable() { + return getMode().equals(FileMode.EXECUTABLE_FILE); + } + + /** + * @param execute set/reset the executable flag + */ + public void setExecutable(final boolean execute) { + mode = execute ? FileMode.EXECUTABLE_FILE : FileMode.REGULAR_FILE; + } + + /** + * @return an {@link ObjectLoader} that will return the data + * @throws IOException + */ + public ObjectLoader openReader() throws IOException { + return getRepository().open(getId(), Constants.OBJ_BLOB); + } + + public String toString() { + final StringBuilder r = new StringBuilder(); + r.append(ObjectId.toString(getId())); + r.append(' '); + r.append(isExecutable() ? 'X' : 'F'); + r.append(' '); + r.append(getFullName()); + return r.toString(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java new file mode 100644 index 000000000..936fd82bf --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2009, Jonas Fonseca + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +/** + * A tree entry representing a gitlink entry used for submodules. + * + * Note. Java cannot really handle these as file system objects. + * + * @deprecated To look up information about a single path, use + * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. + * To lookup information about multiple paths at once, use a + * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's + * information from its getter methods. + */ +@Deprecated +public class GitlinkTreeEntry extends TreeEntry { + + /** + * Construct a {@link GitlinkTreeEntry} with the specified name and SHA-1 in + * the specified parent + * + * @param parent + * @param id + * @param nameUTF8 + */ + public GitlinkTreeEntry(final Tree parent, final ObjectId id, + final byte[] nameUTF8) { + super(parent, id, nameUTF8); + } + + public FileMode getMode() { + return FileMode.GITLINK; + } + + @Override + public String toString() { + final StringBuilder r = new StringBuilder(); + r.append(ObjectId.toString(getId())); + r.append(" G "); //$NON-NLS-1$ + r.append(getFullName()); + return r.toString(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java new file mode 100644 index 000000000..c7e41bce0 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +/** + * A tree entry representing a symbolic link. + * + * Note. Java cannot really handle these as file system objects. + * + * @deprecated To look up information about a single path, use + * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. + * To lookup information about multiple paths at once, use a + * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's + * information from its getter methods. + */ +@Deprecated +public class SymlinkTreeEntry extends TreeEntry { + + /** + * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in + * the specified parent + * + * @param parent + * @param id + * @param nameUTF8 + */ + public SymlinkTreeEntry(final Tree parent, final ObjectId id, + final byte[] nameUTF8) { + super(parent, id, nameUTF8); + } + + public FileMode getMode() { + return FileMode.SYMLINK; + } + + public String toString() { + final StringBuilder r = new StringBuilder(); + r.append(ObjectId.toString(getId())); + r.append(" S "); //$NON-NLS-1$ + r.append(getFullName()); + return r.toString(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java new file mode 100644 index 000000000..43bd489dc --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import java.io.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.EntryExistsException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.ObjectWritingException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.util.RawParseUtils; + +/** + * A representation of a Git tree entry. A Tree is a directory in Git. + * + * @deprecated To look up information about a single path, use + * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. + * To lookup information about multiple paths at once, use a + * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's + * information from its getter methods. + */ +@Deprecated +public class Tree extends TreeEntry { + private static final TreeEntry[] EMPTY_TREE = {}; + + /** + * Compare two names represented as bytes. Since git treats names of trees and + * blobs differently we have one parameter that represents a '/' for trees. For + * other objects the value should be NUL. The names are compare by their positive + * byte value (0..255). + * + * A blob and a tree with the same name will not compare equal. + * + * @param a name + * @param b name + * @param lasta '/' if a is a tree, else NUL + * @param lastb '/' if b is a tree, else NUL + * + * @return < 0 if a is sorted before b, 0 if they are the same, else b + */ + public static final int compareNames(final byte[] a, final byte[] b, final int lasta,final int lastb) { + return compareNames(a, b, 0, b.length, lasta, lastb); + } + + private static final int compareNames(final byte[] a, final byte[] nameUTF8, + final int nameStart, final int nameEnd, final int lasta, int lastb) { + int j,k; + for (j = 0, k = nameStart; j < a.length && k < nameEnd; j++, k++) { + final int aj = a[j] & 0xff; + final int bk = nameUTF8[k] & 0xff; + if (aj < bk) + return -1; + else if (aj > bk) + return 1; + } + if (j < a.length) { + int aj = a[j]&0xff; + if (aj < lastb) + return -1; + else if (aj > lastb) + return 1; + else + if (j == a.length - 1) + return 0; + else + return -1; + } + if (k < nameEnd) { + int bk = nameUTF8[k] & 0xff; + if (lasta < bk) + return -1; + else if (lasta > bk) + return 1; + else + if (k == nameEnd - 1) + return 0; + else + return 1; + } + if (lasta < lastb) + return -1; + else if (lasta > lastb) + return 1; + + final int namelength = nameEnd - nameStart; + if (a.length == namelength) + return 0; + else if (a.length < namelength) + return -1; + else + return 1; + } + + private static final byte[] substring(final byte[] s, final int nameStart, + final int nameEnd) { + if (nameStart == 0 && nameStart == s.length) + return s; + final byte[] n = new byte[nameEnd - nameStart]; + System.arraycopy(s, nameStart, n, 0, n.length); + return n; + } + + private static final int binarySearch(final TreeEntry[] entries, + final byte[] nameUTF8, final int nameUTF8last, final int nameStart, final int nameEnd) { + if (entries.length == 0) + return -1; + int high = entries.length; + int low = 0; + do { + final int mid = (low + high) >>> 1; + final int cmp = compareNames(entries[mid].getNameUTF8(), nameUTF8, + nameStart, nameEnd, TreeEntry.lastChar(entries[mid]), nameUTF8last); + if (cmp < 0) + low = mid + 1; + else if (cmp == 0) + return mid; + else + high = mid; + } while (low < high); + return -(low + 1); + } + + private final Repository db; + + private TreeEntry[] contents; + + /** + * Constructor for a new Tree + * + * @param repo The repository that owns the Tree. + */ + public Tree(final Repository repo) { + super(null, null, null); + db = repo; + contents = EMPTY_TREE; + } + + /** + * Construct a Tree object with known content and hash value + * + * @param repo + * @param myId + * @param raw + * @throws IOException + */ + public Tree(final Repository repo, final ObjectId myId, final byte[] raw) + throws IOException { + super(null, myId, null); + db = repo; + readTree(raw); + } + + /** + * Construct a new Tree under another Tree + * + * @param parent + * @param nameUTF8 + */ + public Tree(final Tree parent, final byte[] nameUTF8) { + super(parent, null, nameUTF8); + db = parent.getRepository(); + contents = EMPTY_TREE; + } + + /** + * Construct a Tree with a known SHA-1 under another tree. Data is not yet + * specified and will have to be loaded on demand. + * + * @param parent + * @param id + * @param nameUTF8 + */ + public Tree(final Tree parent, final ObjectId id, final byte[] nameUTF8) { + super(parent, id, nameUTF8); + db = parent.getRepository(); + } + + public FileMode getMode() { + return FileMode.TREE; + } + + /** + * @return true if this Tree is the top level Tree. + */ + public boolean isRoot() { + return getParent() == null; + } + + public Repository getRepository() { + return db; + } + + /** + * @return true of the data of this Tree is loaded + */ + public boolean isLoaded() { + return contents != null; + } + + /** + * Forget the in-memory data for this tree. + */ + public void unload() { + if (isModified()) + throw new IllegalStateException(JGitText.get().cannotUnloadAModifiedTree); + contents = null; + } + + /** + * Adds a new or existing file with the specified name to this tree. + * Trees are added if necessary as the name may contain '/':s. + * + * @param name Name + * @return a {@link FileTreeEntry} for the added file. + * @throws IOException + */ + public FileTreeEntry addFile(final String name) throws IOException { + return addFile(Repository.gitInternalSlash(Constants.encode(name)), 0); + } + + /** + * Adds a new or existing file with the specified name to this tree. + * Trees are added if necessary as the name may contain '/':s. + * + * @param s an array containing the name + * @param offset when the name starts in the tree. + * + * @return a {@link FileTreeEntry} for the added file. + * @throws IOException + */ + public FileTreeEntry addFile(final byte[] s, final int offset) + throws IOException { + int slash; + int p; + + for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { + // search for path component terminator + } + + ensureLoaded(); + byte xlast = slash= 0 && slash < s.length && contents[p] instanceof Tree) + return ((Tree) contents[p]).addFile(s, slash + 1); + + final byte[] newName = substring(s, offset, slash); + if (p >= 0) + throw new EntryExistsException(RawParseUtils.decode(newName)); + else if (slash < s.length) { + final Tree t = new Tree(this, newName); + insertEntry(p, t); + return t.addFile(s, slash + 1); + } else { + final FileTreeEntry f = new FileTreeEntry(this, null, newName, + false); + insertEntry(p, f); + return f; + } + } + + /** + * Adds a new or existing Tree with the specified name to this tree. + * Trees are added if necessary as the name may contain '/':s. + * + * @param name Name + * @return a {@link FileTreeEntry} for the added tree. + * @throws IOException + */ + public Tree addTree(final String name) throws IOException { + return addTree(Repository.gitInternalSlash(Constants.encode(name)), 0); + } + + /** + * Adds a new or existing Tree with the specified name to this tree. + * Trees are added if necessary as the name may contain '/':s. + * + * @param s an array containing the name + * @param offset when the name starts in the tree. + * + * @return a {@link FileTreeEntry} for the added tree. + * @throws IOException + */ + public Tree addTree(final byte[] s, final int offset) throws IOException { + int slash; + int p; + + for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { + // search for path component terminator + } + + ensureLoaded(); + p = binarySearch(contents, s, (byte)'/', offset, slash); + if (p >= 0 && slash < s.length && contents[p] instanceof Tree) + return ((Tree) contents[p]).addTree(s, slash + 1); + + final byte[] newName = substring(s, offset, slash); + if (p >= 0) + throw new EntryExistsException(RawParseUtils.decode(newName)); + + final Tree t = new Tree(this, newName); + insertEntry(p, t); + return slash == s.length ? t : t.addTree(s, slash + 1); + } + + /** + * Add the specified tree entry to this tree. + * + * @param e + * @throws IOException + */ + public void addEntry(final TreeEntry e) throws IOException { + final int p; + + ensureLoaded(); + p = binarySearch(contents, e.getNameUTF8(), TreeEntry.lastChar(e), 0, e.getNameUTF8().length); + if (p < 0) { + e.attachParent(this); + insertEntry(p, e); + } else { + throw new EntryExistsException(e.getName()); + } + } + + private void insertEntry(int p, final TreeEntry e) { + final TreeEntry[] c = contents; + final TreeEntry[] n = new TreeEntry[c.length + 1]; + p = -(p + 1); + for (int k = c.length - 1; k >= p; k--) + n[k + 1] = c[k]; + n[p] = e; + for (int k = p - 1; k >= 0; k--) + n[k] = c[k]; + contents = n; + setModified(); + } + + void removeEntry(final TreeEntry e) { + final TreeEntry[] c = contents; + final int p = binarySearch(c, e.getNameUTF8(), TreeEntry.lastChar(e), 0, + e.getNameUTF8().length); + if (p >= 0) { + final TreeEntry[] n = new TreeEntry[c.length - 1]; + for (int k = c.length - 1; k > p; k--) + n[k - 1] = c[k]; + for (int k = p - 1; k >= 0; k--) + n[k] = c[k]; + contents = n; + setModified(); + } + } + + /** + * @return number of members in this tree + * @throws IOException + */ + public int memberCount() throws IOException { + ensureLoaded(); + return contents.length; + } + + /** + * Return all members of the tree sorted in Git order. + * + * Entries are sorted by the numerical unsigned byte + * values with (sub)trees having an implicit '/'. An + * example of a tree with three entries. a:b is an + * actual file name here. + * + *

+ * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a.b + * 040000 tree 4277b6e69d25e5efa77c455340557b384a4c018a a + * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a:b + * + * @return all entries in this Tree, sorted. + * @throws IOException + */ + public TreeEntry[] members() throws IOException { + ensureLoaded(); + final TreeEntry[] c = contents; + if (c.length != 0) { + final TreeEntry[] r = new TreeEntry[c.length]; + for (int k = c.length - 1; k >= 0; k--) + r[k] = c[k]; + return r; + } else + return c; + } + + private boolean exists(final String s, byte slast) throws IOException { + return findMember(s, slast) != null; + } + + /** + * @param path to the tree. + * @return true if a tree with the specified path can be found under this + * tree. + * @throws IOException + */ + public boolean existsTree(String path) throws IOException { + return exists(path,(byte)'/'); + } + + /** + * @param path of the non-tree entry. + * @return true if a blob, symlink, or gitlink with the specified name + * can be found under this tree. + * @throws IOException + */ + public boolean existsBlob(String path) throws IOException { + return exists(path,(byte)0); + } + + private TreeEntry findMember(final String s, byte slast) throws IOException { + return findMember(Repository.gitInternalSlash(Constants.encode(s)), slast, 0); + } + + private TreeEntry findMember(final byte[] s, final byte slast, final int offset) + throws IOException { + int slash; + int p; + + for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { + // search for path component terminator + } + + ensureLoaded(); + byte xlast = slash= 0) { + final TreeEntry r = contents[p]; + if (slash < s.length-1) + return r instanceof Tree ? ((Tree) r).findMember(s, slast, slash + 1) + : null; + return r; + } + return null; + } + + /** + * @param s + * blob name + * @return a {@link TreeEntry} representing an object with the specified + * relative path. + * @throws IOException + */ + public TreeEntry findBlobMember(String s) throws IOException { + return findMember(s,(byte)0); + } + + /** + * @param s Tree Name + * @return a Tree with the name s or null + * @throws IOException + */ + public TreeEntry findTreeMember(String s) throws IOException { + return findMember(s,(byte)'/'); + } + + private void ensureLoaded() throws IOException, MissingObjectException { + if (!isLoaded()) { + ObjectLoader ldr = db.open(getId(), Constants.OBJ_TREE); + readTree(ldr.getCachedBytes()); + } + } + + private void readTree(final byte[] raw) throws IOException { + final int rawSize = raw.length; + int rawPtr = 0; + TreeEntry[] temp; + int nextIndex = 0; + + while (rawPtr < rawSize) { + while (rawPtr < rawSize && raw[rawPtr] != 0) + rawPtr++; + rawPtr++; + rawPtr += Constants.OBJECT_ID_LENGTH; + nextIndex++; + } + + temp = new TreeEntry[nextIndex]; + rawPtr = 0; + nextIndex = 0; + while (rawPtr < rawSize) { + int c = raw[rawPtr++]; + if (c < '0' || c > '7') + throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidEntryMode); + int mode = c - '0'; + for (;;) { + c = raw[rawPtr++]; + if (' ' == c) + break; + else if (c < '0' || c > '7') + throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidMode); + mode <<= 3; + mode += c - '0'; + } + + int nameLen = 0; + while (raw[rawPtr + nameLen] != 0) + nameLen++; + final byte[] name = new byte[nameLen]; + System.arraycopy(raw, rawPtr, name, 0, nameLen); + rawPtr += nameLen + 1; + + final ObjectId id = ObjectId.fromRaw(raw, rawPtr); + rawPtr += Constants.OBJECT_ID_LENGTH; + + final TreeEntry ent; + if (FileMode.REGULAR_FILE.equals(mode)) + ent = new FileTreeEntry(this, id, name, false); + else if (FileMode.EXECUTABLE_FILE.equals(mode)) + ent = new FileTreeEntry(this, id, name, true); + else if (FileMode.TREE.equals(mode)) + ent = new Tree(this, id, name); + else if (FileMode.SYMLINK.equals(mode)) + ent = new SymlinkTreeEntry(this, id, name); + else if (FileMode.GITLINK.equals(mode)) + ent = new GitlinkTreeEntry(this, id, name); + else + throw new CorruptObjectException(getId(), MessageFormat.format( + JGitText.get().corruptObjectInvalidMode2, Integer.toOctalString(mode))); + temp[nextIndex++] = ent; + } + + contents = temp; + } + + /** + * Format this Tree in canonical format. + * + * @return canonical encoding of the tree object. + * @throws IOException + * the tree cannot be loaded, or its not in a writable state. + */ + public byte[] format() throws IOException { + TreeFormatter fmt = new TreeFormatter(); + for (TreeEntry e : members()) { + ObjectId id = e.getId(); + if (id == null) + throw new ObjectWritingException(MessageFormat.format(JGitText + .get().objectAtPathDoesNotHaveId, e.getFullName())); + + fmt.append(e.getNameUTF8(), e.getMode(), id); + } + return fmt.toByteArray(); + } + + public String toString() { + final StringBuilder r = new StringBuilder(); + r.append(ObjectId.toString(getId())); + r.append(" T "); //$NON-NLS-1$ + r.append(getFullName()); + return r.toString(); + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java new file mode 100644 index 000000000..a1ffa6805 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import java.io.IOException; + +import org.eclipse.jgit.util.RawParseUtils; + +/** + * This class represents an entry in a tree, like a blob or another tree. + * + * @deprecated To look up information about a single path, use + * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. + * To lookup information about multiple paths at once, use a + * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's + * information from its getter methods. + */ +@Deprecated +public abstract class TreeEntry implements Comparable { + private byte[] nameUTF8; + + private Tree parent; + + private ObjectId id; + + /** + * Construct a named tree entry. + * + * @param myParent + * @param myId + * @param myNameUTF8 + */ + protected TreeEntry(final Tree myParent, final ObjectId myId, + final byte[] myNameUTF8) { + nameUTF8 = myNameUTF8; + parent = myParent; + id = myId; + } + + /** + * @return parent of this tree. + */ + public Tree getParent() { + return parent; + } + + /** + * Delete this entry. + */ + public void delete() { + getParent().removeEntry(this); + detachParent(); + } + + /** + * Detach this entry from it's parent. + */ + public void detachParent() { + parent = null; + } + + void attachParent(final Tree p) { + parent = p; + } + + /** + * @return the repository owning this entry. + */ + public Repository getRepository() { + return getParent().getRepository(); + } + + /** + * @return the raw byte name of this entry. + */ + public byte[] getNameUTF8() { + return nameUTF8; + } + + /** + * @return the name of this entry. + */ + public String getName() { + if (nameUTF8 != null) + return RawParseUtils.decode(nameUTF8); + return null; + } + + /** + * Rename this entry. + * + * @param n The new name + * @throws IOException + */ + public void rename(final String n) throws IOException { + rename(Constants.encode(n)); + } + + /** + * Rename this entry. + * + * @param n The new name + * @throws IOException + */ + public void rename(final byte[] n) throws IOException { + final Tree t = getParent(); + if (t != null) { + delete(); + } + nameUTF8 = n; + if (t != null) { + t.addEntry(this); + } + } + + /** + * @return true if this entry is new or modified since being loaded. + */ + public boolean isModified() { + return getId() == null; + } + + /** + * Mark this entry as modified. + */ + public void setModified() { + setId(null); + } + + /** + * @return SHA-1 of this tree entry (null for new unhashed entries) + */ + public ObjectId getId() { + return id; + } + + /** + * Set (update) the SHA-1 of this entry. Invalidates the id's of all + * entries above this entry as they will have to be recomputed. + * + * @param n SHA-1 for this entry. + */ + public void setId(final ObjectId n) { + // If we have a parent and our id is being cleared or changed then force + // the parent's id to become unset as it depends on our id. + // + final Tree p = getParent(); + if (p != null && id != n) { + if ((id == null && n != null) || (id != null && n == null) + || !id.equals(n)) { + p.setId(null); + } + } + + id = n; + } + + /** + * @return repository relative name of this entry + */ + public String getFullName() { + final StringBuilder r = new StringBuilder(); + appendFullName(r); + return r.toString(); + } + + /** + * @return repository relative name of the entry + * FIXME better encoding + */ + public byte[] getFullNameUTF8() { + return getFullName().getBytes(); + } + + public int compareTo(final Object o) { + if (this == o) + return 0; + if (o instanceof TreeEntry) + return Tree.compareNames(nameUTF8, ((TreeEntry) o).nameUTF8, lastChar(this), lastChar((TreeEntry)o)); + return -1; + } + + /** + * Helper for accessing tree/blob methods. + * + * @param treeEntry + * @return '/' for Tree entries and NUL for non-treeish objects. + */ + final public static int lastChar(TreeEntry treeEntry) { + if (!(treeEntry instanceof Tree)) + return '\0'; + else + return '/'; + } + + /** + * @return mode (type of object) + */ + public abstract FileMode getMode(); + + private void appendFullName(final StringBuilder r) { + final TreeEntry p = getParent(); + final String n = getName(); + if (p != null) { + p.appendFullName(r); + if (r.length() > 0) { + r.append('/'); + } + } + if (n != null) { + r.append(n); + } + } +} From eadfcd3ec166c55c1ff3f3fe0b5e97dd94ff8d83 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Mon, 18 Jan 2016 10:42:00 -0800 Subject: [PATCH 80/89] ReceiveCommand.abort(): Utility to mark batch of commands as failed If one or more commands is failing the entire group usually has to also fail with "transaction aborted". Pull this loop into a helper so the idiom can be easily reused in several places throughout JGit. Change-Id: I3b9399b7e26ce2b0dc5f7baa85d585a433b4eaed --- .../storage/dfs/InMemoryRepository.java | 18 +++---------- .../internal/storage/reftree/Command.java | 26 +++++++++++++++++++ .../internal/storage/reftree/RefTree.java | 23 +++++----------- .../storage/reftree/RefTreeBatch.java | 26 +++---------------- .../jgit/transport/BaseReceivePack.java | 5 +--- .../jgit/transport/ReceiveCommand.java | 22 ++++++++++++++++ 6 files changed, 63 insertions(+), 57 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java index a050e1a5b..5e246b47b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java @@ -16,7 +16,6 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.ObjectId; @@ -312,7 +311,7 @@ private void batch(RevWalk walk, List cmds) { try (RevWalk rw = new RevWalk(getRepository())) { for (ReceiveCommand c : cmds) { if (c.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) { - reject(cmds); + ReceiveCommand.abort(cmds); return; } @@ -324,7 +323,7 @@ private void batch(RevWalk walk, List cmds) { } } catch (IOException e) { c.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT); - reject(cmds); + ReceiveCommand.abort(cmds); return; } } @@ -337,7 +336,7 @@ private void batch(RevWalk walk, List cmds) { if (r == null) { if (c.getType() != ReceiveCommand.Type.CREATE) { c.setResult(ReceiveCommand.Result.LOCK_FAILURE); - reject(cmds); + ReceiveCommand.abort(cmds); return; } } else { @@ -345,7 +344,7 @@ private void batch(RevWalk walk, List cmds) { if (r.isSymbolic() || objectId == null || !objectId.equals(c.getOldId())) { c.setResult(ReceiveCommand.Result.LOCK_FAILURE); - reject(cmds); + ReceiveCommand.abort(cmds); return; } } @@ -374,15 +373,6 @@ private void batch(RevWalk walk, List cmds) { clearCache(); } - private void reject(List cmds) { - for (ReceiveCommand c : cmds) { - if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) { - c.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, - JGitText.get().transactionAborted); - } - } - } - @Override protected boolean compareAndPut(Ref oldRef, Ref newRef) throws IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java index 540c4384a..dd08375f2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java @@ -49,12 +49,14 @@ import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK; import static org.eclipse.jgit.lib.Ref.Storage.NETWORK; import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; import java.io.IOException; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ObjectInserter; @@ -79,6 +81,30 @@ * for processing. */ public class Command { + /** + * Set unprocessed commands as failed due to transaction aborted. + *

+ * If a command is still {@link Result#NOT_ATTEMPTED} it will be set to + * {@link Result#REJECTED_OTHER_REASON}. If {@code why} is non-null its + * contents will be used as the message for the first command status. + * + * @param commands + * commands to mark as failed. + * @param why + * optional message to set on the first aborted command. + */ + public static void abort(Iterable commands, @Nullable String why) { + if (why == null || why.isEmpty()) { + why = JGitText.get().transactionAborted; + } + for (Command c : commands) { + if (c.getResult() == NOT_ATTEMPTED) { + c.setResult(REJECTED_OTHER_REASON, why); + why = JGitText.get().transactionAborted; + } + } + } + private final Ref oldRef; private final Ref newRef; private final ReceiveCommand cmd; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java index 43eec5185..85690c8ca 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java @@ -55,7 +55,6 @@ import static org.eclipse.jgit.lib.Ref.Storage.PACKED; import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH; import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE; -import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; import java.io.IOException; @@ -248,7 +247,8 @@ public boolean apply(Collection cmdList) { if (!isValidRef(cmd)) { cmd.setResult(REJECTED_OTHER_REASON, JGitText.get().funnyRefname); - return abort(cmdList); + Command.abort(cmdList, null); + return false; } apply(ed, cmd); } @@ -264,9 +264,11 @@ public boolean apply(Collection cmdList) { break; } } - return abort(cmdList); + Command.abort(cmdList, null); + return false; } catch (LockFailureException e) { - return abort(cmdList); + Command.abort(cmdList, null); + return false; } } @@ -342,19 +344,6 @@ private static void cleanupPeeledRef(DirCacheEditor ed, Ref ref) { } } - private static boolean abort(Iterable cmdList) { - for (Command cmd : cmdList) { - if (cmd.getResult() == NOT_ATTEMPTED) { - reject(cmd, JGitText.get().transactionAborted); - } - } - return false; - } - - private static void reject(Command cmd, String msg) { - cmd.setResult(REJECTED_OTHER_REASON, msg); - } - /** * Convert a path name in a RefTree to the reference name known by Git. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java index 0cedea94d..a55a9f51e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java @@ -98,7 +98,7 @@ public void execute(RevWalk rw, ProgressMonitor monitor) } if (c.getType() == UPDATE_NONFASTFORWARD) { c.setResult(REJECTED_NONFASTFORWARD); - reject(); + ReceiveCommand.abort(getCommands()); return; } } @@ -108,15 +108,6 @@ public void execute(RevWalk rw, ProgressMonitor monitor) execute(rw, todo); } - private void reject() { - String aborted = JGitText.get().transactionAborted; - for (ReceiveCommand c : getCommands()) { - if (c.getResult() == NOT_ATTEMPTED) { - c.setResult(REJECTED_OTHER_REASON, aborted); - } - } - } - void init(RevWalk rw) throws IOException { src = refdb.getBootstrap().exactRef(refdb.getTxnCommitted()); if (src != null && src.getObjectId() != null) { @@ -150,13 +141,13 @@ Ref exactRef(ObjectReader reader, String name) throws IOException { void execute(RevWalk rw, List todo) throws IOException { for (Command c : todo) { if (c.getResult() != NOT_ATTEMPTED) { - reject(todo, JGitText.get().transactionAborted); + Command.abort(todo, null); return; } if (refdb.conflictsWithBootstrap(c.getRefName())) { c.setResult(REJECTED_OTHER_REASON, MessageFormat .format(JGitText.get().invalidRefName, c.getRefName())); - reject(todo, JGitText.get().transactionAborted); + Command.abort(todo, null); return; } } @@ -210,7 +201,7 @@ private void commit(RevWalk rw, List todo) throws IOException { c.setResult(OK); } } else { - reject(todo, commit.getResult().name()); + Command.abort(todo, commit.getResult().name()); } } @@ -228,13 +219,4 @@ private void updateBootstrap(RevWalk rw, ReceiveCommand commit) u.addCommand(commit); u.execute(rw, NullProgressMonitor.INSTANCE); } - - private static void reject(List todo, String msg) { - for (Command c : todo) { - if (c.getResult() == NOT_ATTEMPTED) { - c.setResult(REJECTED_OTHER_REASON, msg); - msg = JGitText.get().transactionAborted; - } - } - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java index 728643e92..a20e65255 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java @@ -1453,10 +1453,7 @@ protected boolean anyRejects() { * @since 3.6 */ protected void failPendingCommands() { - for (ReceiveCommand cmd : commands) { - if (cmd.getResult() == Result.NOT_ATTEMPTED) - cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().transactionAborted); - } + ReceiveCommand.abort(commands); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java index 5702b6d7b..2b21c4a8f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java @@ -43,6 +43,9 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; + import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; @@ -168,6 +171,25 @@ public static List filter(List commands, return filter((Iterable) commands, want); } + /** + * Set unprocessed commands as failed due to transaction aborted. + *

+ * If a command is still {@link Result#NOT_ATTEMPTED} it will be set to + * {@link Result#REJECTED_OTHER_REASON}. + * + * @param commands + * commands to mark as failed. + * @since 4.2 + */ + public static void abort(Iterable commands) { + for (ReceiveCommand c : commands) { + if (c.getResult() == NOT_ATTEMPTED) { + c.setResult(REJECTED_OTHER_REASON, + JGitText.get().transactionAborted); + } + } + } + private final ObjectId oldId; private final ObjectId newId; From 2006e90abc24926ae606e1a50b27f06df2226173 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Tue, 19 Jan 2016 14:27:41 +0100 Subject: [PATCH 81/89] Annotated to be removed Tree API with @noreference and @noextend See https://wiki.eclipse.org/Eclipse/API_Central/API_Removal_Process. Bug: 486105 Change-Id: I460e43da0d487279608729a2081c614e7065f56f Signed-off-by: Andrey Loskutov --- org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java | 2 ++ org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java | 2 ++ org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java | 2 ++ org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java | 2 ++ org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java | 2 ++ 5 files changed, 10 insertions(+) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java index 6811417ee..9c689440b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java @@ -54,6 +54,8 @@ * To lookup information about multiple paths at once, use a * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's * information from its getter methods. + * @noreference This class is not intended to be referenced by clients. + * @noextend This class is not intended to be subclassed by clients. */ @Deprecated public class FileTreeEntry extends TreeEntry { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java index 936fd82bf..f42f7a221 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java @@ -55,6 +55,8 @@ * To lookup information about multiple paths at once, use a * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's * information from its getter methods. + * @noreference This class is not intended to be referenced by clients. + * @noextend This class is not intended to be subclassed by clients. */ @Deprecated public class GitlinkTreeEntry extends TreeEntry { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java index c7e41bce0..5ff83260d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java @@ -54,6 +54,8 @@ * To lookup information about multiple paths at once, use a * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's * information from its getter methods. + * @noreference This class is not intended to be referenced by clients. + * @noextend This class is not intended to be subclassed by clients. */ @Deprecated public class SymlinkTreeEntry extends TreeEntry { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java index 43bd489dc..94d14401d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java @@ -63,6 +63,8 @@ * To lookup information about multiple paths at once, use a * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's * information from its getter methods. + * @noreference This class is not intended to be referenced by clients. + * @noextend This class is not intended to be subclassed by clients. */ @Deprecated public class Tree extends TreeEntry { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java index a1ffa6805..83be44cf3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java @@ -56,6 +56,8 @@ * To lookup information about multiple paths at once, use a * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's * information from its getter methods. + * @noreference This class is not intended to be referenced by clients. + * @noextend This class is not intended to be subclassed by clients. */ @Deprecated public abstract class TreeEntry implements Comparable { From 7f31a9c9ad0da4a9b8c42bded0a610621bd7b130 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Tue, 19 Jan 2016 12:22:36 -0500 Subject: [PATCH 82/89] Revert "Revert "Remove deprecated Tree, TreeEntry, FileTreeEntry and friends"" This reverts commit 2cc80187d3633adedc99eb97132e0a749b457c19. Bug: 486105 Change-Id: Id4f9987c33d66cbed9de6e4d4d6784afdd01a3cf Signed-off-by: Andrey Loskutov --- .../storage/file/T0003_BasicTest.java | 53 +- .../org/eclipse/jgit/lib/IndexDiffTest.java | 91 ++- .../org/eclipse/jgit/lib/T0002_TreeTest.java | 319 --------- .../eclipse/jgit/revwalk/ObjectWalkTest.java | 39 +- .../jgit/treewalk/TreeWalkBasicDiffTest.java | 77 ++- .../org/eclipse/jgit/lib/FileTreeEntry.java | 117 ---- .../eclipse/jgit/lib/GitlinkTreeEntry.java | 89 --- .../eclipse/jgit/lib/SymlinkTreeEntry.java | 87 --- .../src/org/eclipse/jgit/lib/Tree.java | 603 ------------------ .../src/org/eclipse/jgit/lib/TreeEntry.java | 258 -------- 10 files changed, 108 insertions(+), 1625 deletions(-) delete mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java delete mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java index 5670a9634..a100ff357 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java @@ -67,7 +67,6 @@ import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.FileTreeEntry; import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; @@ -75,7 +74,6 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.TagBuilder; -import org.eclipse.jgit.lib.Tree; import org.eclipse.jgit.lib.TreeFormatter; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTag; @@ -418,29 +416,6 @@ public void test009_CreateCommitOldFormat() throws IOException { assertEquals(c.getCommitter(), c2.getCommitterIdent()); } - @Test - public void test012_SubtreeExternalSorting() throws IOException { - final ObjectId emptyBlob = insertEmptyBlob(); - final Tree t = new Tree(db); - final FileTreeEntry e0 = t.addFile("a-"); - final FileTreeEntry e1 = t.addFile("a-b"); - final FileTreeEntry e2 = t.addFile("a/b"); - final FileTreeEntry e3 = t.addFile("a="); - final FileTreeEntry e4 = t.addFile("a=b"); - - e0.setId(emptyBlob); - e1.setId(emptyBlob); - e2.setId(emptyBlob); - e3.setId(emptyBlob); - e4.setId(emptyBlob); - - final Tree a = (Tree) t.findTreeMember("a"); - a.setId(insertTree(a)); - assertEquals(ObjectId - .fromString("b47a8f0a4190f7572e11212769090523e23eb1ea"), - insertTree(t)); - } - @Test public void test020_createBlobTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); @@ -464,9 +439,8 @@ public void test020_createBlobTag() throws IOException { @Test public void test021_createTreeTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); final TagBuilder t = new TagBuilder(); t.setObjectId(almostEmptyTreeId, Constants.OBJ_TREE); @@ -488,9 +462,8 @@ public void test021_createTreeTag() throws IOException { @Test public void test022_createCommitTag() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); final CommitBuilder almostEmptyCommit = new CommitBuilder(); almostEmptyCommit.setAuthor(new PersonIdent(author, 1154236443000L, @@ -520,9 +493,8 @@ public void test022_createCommitTag() throws IOException { @Test public void test023_createCommitNonAnullii() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); CommitBuilder commit = new CommitBuilder(); commit.setTreeId(almostEmptyTreeId); @@ -542,9 +514,8 @@ public void test023_createCommitNonAnullii() throws IOException { @Test public void test024_createCommitNonAscii() throws IOException { final ObjectId emptyId = insertEmptyBlob(); - final Tree almostEmptyTree = new Tree(db); - almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, - "empty".getBytes(), false)); + TreeFormatter almostEmptyTree = new TreeFormatter(); + almostEmptyTree.append("empty", FileMode.REGULAR_FILE, emptyId); final ObjectId almostEmptyTreeId = insertTree(almostEmptyTree); CommitBuilder commit = new CommitBuilder(); commit.setTreeId(almostEmptyTreeId); @@ -746,14 +717,6 @@ private ObjectId insertEmptyBlob() throws IOException { return emptyId; } - private ObjectId insertTree(Tree tree) throws IOException { - try (ObjectInserter oi = db.newObjectInserter()) { - ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format()); - oi.flush(); - return id; - } - } - private ObjectId insertTree(TreeFormatter tree) throws IOException { try (ObjectInserter oi = db.newObjectInserter()) { ObjectId id = oi.insert(tree); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java index a5cd7b5c0..7fcee3dc8 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java @@ -99,8 +99,7 @@ public void apply(DirCacheEntry ent) { public void testAdded() throws IOException { writeTrashFile("file1", "file1"); writeTrashFile("dir/subfile", "dir/subfile"); - Tree tree = new Tree(db); - tree.setId(insertTree(tree)); + ObjectId tree = insertTree(new TreeFormatter()); DirCache index = db.lockDirCache(); DirCacheEditor editor = index.editor(); @@ -108,7 +107,7 @@ public void testAdded() throws IOException { editor.add(add(db, trash, "dir/subfile")); editor.commit(); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, tree, iterator); diff.diff(); assertEquals(2, diff.getAdded().size()); assertTrue(diff.getAdded().contains("file1")); @@ -124,18 +123,16 @@ public void testRemoved() throws IOException { writeTrashFile("file2", "file2"); writeTrashFile("dir/file3", "dir/file3"); - Tree tree = new Tree(db); - tree.addFile("file2"); - tree.addFile("dir/file3"); - assertEquals(2, tree.memberCount()); - tree.findBlobMember("file2").setId(ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad")); - Tree tree2 = (Tree) tree.findTreeMember("dir"); - tree2.findBlobMember("file3").setId(ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b")); - tree2.setId(insertTree(tree2)); - tree.setId(insertTree(tree)); + TreeFormatter dir = new TreeFormatter(); + dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b")); + + TreeFormatter tree = new TreeFormatter(); + tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad")); + tree.append("dir", FileMode.TREE, insertTree(dir)); + ObjectId treeId = insertTree(tree); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(2, diff.getRemoved().size()); assertTrue(diff.getRemoved().contains("file2")); @@ -157,16 +154,16 @@ public void testModified() throws IOException, GitAPIException { writeTrashFile("dir/file3", "changed"); - Tree tree = new Tree(db); - tree.addFile("file2").setId(ObjectId.fromString("0123456789012345678901234567890123456789")); - tree.addFile("dir/file3").setId(ObjectId.fromString("0123456789012345678901234567890123456789")); - assertEquals(2, tree.memberCount()); + TreeFormatter dir = new TreeFormatter(); + dir.append("file3", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789")); + + TreeFormatter tree = new TreeFormatter(); + tree.append("dir", FileMode.TREE, insertTree(dir)); + tree.append("file2", FileMode.REGULAR_FILE, ObjectId.fromString("0123456789012345678901234567890123456789")); + ObjectId treeId = insertTree(tree); - Tree tree2 = (Tree) tree.findTreeMember("dir"); - tree2.setId(insertTree(tree2)); - tree.setId(insertTree(tree)); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(2, diff.getChanged().size()); assertTrue(diff.getChanged().contains("file2")); @@ -314,17 +311,16 @@ public void testUnchangedSimple() throws IOException, GitAPIException { git.add().addFilepattern("a=c").call(); git.add().addFilepattern("a=d").call(); - Tree tree = new Tree(db); + TreeFormatter tree = new TreeFormatter(); // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin - tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); - tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); - tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); - tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); - - tree.setId(insertTree(tree)); + tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); + tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); + tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); + tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); + ObjectId treeId = insertTree(tree); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getAdded().size()); @@ -356,24 +352,27 @@ public void testUnchangedComplex() throws IOException, GitAPIException { .addFilepattern("a/c").addFilepattern("a=c") .addFilepattern("a=d").call(); - Tree tree = new Tree(db); - // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin - tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); - tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); - tree.addFile("a/b.b/b").setId(ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd")); - tree.addFile("a/b").setId(ObjectId.fromString("db89c972fc57862eae378f45b74aca228037d415")); - tree.addFile("a/c").setId(ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007")); - tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); - tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); - Tree tree3 = (Tree) tree.findTreeMember("a/b.b"); - tree3.setId(insertTree(tree3)); - Tree tree2 = (Tree) tree.findTreeMember("a"); - tree2.setId(insertTree(tree2)); - tree.setId(insertTree(tree)); + // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin + TreeFormatter bb = new TreeFormatter(); + bb.append("b", FileMode.REGULAR_FILE, ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd")); + + TreeFormatter a = new TreeFormatter(); + a.append("b", FileMode.REGULAR_FILE, ObjectId + .fromString("db89c972fc57862eae378f45b74aca228037d415")); + a.append("b.b", FileMode.TREE, insertTree(bb)); + a.append("c", FileMode.REGULAR_FILE, ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007")); + + TreeFormatter tree = new TreeFormatter(); + tree.append("a.b", FileMode.REGULAR_FILE, ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); + tree.append("a.c", FileMode.REGULAR_FILE, ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); + tree.append("a", FileMode.TREE, insertTree(a)); + tree.append("a=c", FileMode.REGULAR_FILE, ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); + tree.append("a=d", FileMode.REGULAR_FILE, ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); + ObjectId treeId = insertTree(tree); FileTreeIterator iterator = new FileTreeIterator(db); - IndexDiff diff = new IndexDiff(db, tree.getId(), iterator); + IndexDiff diff = new IndexDiff(db, treeId, iterator); diff.diff(); assertEquals(0, diff.getChanged().size()); assertEquals(0, diff.getAdded().size()); @@ -383,9 +382,9 @@ public void testUnchangedComplex() throws IOException, GitAPIException { assertEquals(Collections.EMPTY_SET, diff.getUntrackedFolders()); } - private ObjectId insertTree(Tree tree) throws IOException { + private ObjectId insertTree(TreeFormatter tree) throws IOException { try (ObjectInserter oi = db.newObjectInserter()) { - ObjectId id = oi.insert(Constants.OBJ_TREE, tree.format()); + ObjectId id = oi.insert(tree); oi.flush(); return id; } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java deleted file mode 100644 index 651e62c9c..000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (C) 2008, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; -import org.junit.Test; - -@SuppressWarnings("deprecation") -public class T0002_TreeTest extends SampleDataRepositoryTestCase { - private static final ObjectId SOME_FAKE_ID = ObjectId.fromString( - "0123456789abcdef0123456789abcdef01234567"); - - private static int compareNamesUsingSpecialCompare(String a, String b) - throws UnsupportedEncodingException { - char lasta = '\0'; - byte[] abytes; - if (a.length() > 0 && a.charAt(a.length()-1) == '/') { - lasta = '/'; - a = a.substring(0, a.length() - 1); - } - abytes = a.getBytes("ISO-8859-1"); - char lastb = '\0'; - byte[] bbytes; - if (b.length() > 0 && b.charAt(b.length()-1) == '/') { - lastb = '/'; - b = b.substring(0, b.length() - 1); - } - bbytes = b.getBytes("ISO-8859-1"); - return Tree.compareNames(abytes, bbytes, lasta, lastb); - } - - @Test - public void test000_sort_01() throws UnsupportedEncodingException { - assertEquals(0, compareNamesUsingSpecialCompare("a","a")); - } - - @Test - public void test000_sort_02() throws UnsupportedEncodingException { - assertEquals(-1, compareNamesUsingSpecialCompare("a","b")); - assertEquals(1, compareNamesUsingSpecialCompare("b","a")); - } - - @Test - public void test000_sort_03() throws UnsupportedEncodingException { - assertEquals(1, compareNamesUsingSpecialCompare("a:","a")); - assertEquals(1, compareNamesUsingSpecialCompare("a/","a")); - assertEquals(-1, compareNamesUsingSpecialCompare("a","a/")); - assertEquals(-1, compareNamesUsingSpecialCompare("a","a:")); - assertEquals(1, compareNamesUsingSpecialCompare("a:","a/")); - assertEquals(-1, compareNamesUsingSpecialCompare("a/","a:")); - } - - @Test - public void test000_sort_04() throws UnsupportedEncodingException { - assertEquals(-1, compareNamesUsingSpecialCompare("a.a","a/a")); - assertEquals(1, compareNamesUsingSpecialCompare("a/a","a.a")); - } - - @Test - public void test000_sort_05() throws UnsupportedEncodingException { - assertEquals(-1, compareNamesUsingSpecialCompare("a.","a/")); - assertEquals(1, compareNamesUsingSpecialCompare("a/","a.")); - - } - - @Test - public void test001_createEmpty() throws IOException { - final Tree t = new Tree(db); - assertTrue("isLoaded", t.isLoaded()); - assertTrue("isModified", t.isModified()); - assertTrue("no parent", t.getParent() == null); - assertTrue("isRoot", t.isRoot()); - assertTrue("no name", t.getName() == null); - assertTrue("no nameUTF8", t.getNameUTF8() == null); - assertTrue("has entries array", t.members() != null); - assertEquals("entries is empty", 0, t.members().length); - assertEquals("full name is empty", "", t.getFullName()); - assertTrue("no id", t.getId() == null); - assertTrue("database is r", t.getRepository() == db); - assertTrue("no foo child", t.findTreeMember("foo") == null); - assertTrue("no foo child", t.findBlobMember("foo") == null); - } - - @Test - public void test002_addFile() throws IOException { - final Tree t = new Tree(db); - t.setId(SOME_FAKE_ID); - assertTrue("has id", t.getId() != null); - assertFalse("not modified", t.isModified()); - - final String n = "bob"; - final FileTreeEntry f = t.addFile(n); - assertNotNull("have file", f); - assertEquals("name matches", n, f.getName()); - assertEquals("name matches", f.getName(), new String(f.getNameUTF8(), - "UTF-8")); - assertEquals("full name matches", n, f.getFullName()); - assertTrue("no id", f.getId() == null); - assertTrue("is modified", t.isModified()); - assertTrue("has no id", t.getId() == null); - assertTrue("found bob", t.findBlobMember(f.getName()) == f); - - final TreeEntry[] i = t.members(); - assertNotNull("members array not null", i); - assertTrue("iterator is not empty", i != null && i.length > 0); - assertTrue("iterator returns file", i != null && i[0] == f); - assertTrue("iterator is empty", i != null && i.length == 1); - } - - @Test - public void test004_addTree() throws IOException { - final Tree t = new Tree(db); - t.setId(SOME_FAKE_ID); - assertTrue("has id", t.getId() != null); - assertFalse("not modified", t.isModified()); - - final String n = "bob"; - final Tree f = t.addTree(n); - assertNotNull("have tree", f); - assertEquals("name matches", n, f.getName()); - assertEquals("name matches", f.getName(), new String(f.getNameUTF8(), - "UTF-8")); - assertEquals("full name matches", n, f.getFullName()); - assertTrue("no id", f.getId() == null); - assertTrue("parent matches", f.getParent() == t); - assertTrue("repository matches", f.getRepository() == db); - assertTrue("isLoaded", f.isLoaded()); - assertFalse("has items", f.members().length > 0); - assertFalse("is root", f.isRoot()); - assertTrue("parent is modified", t.isModified()); - assertTrue("parent has no id", t.getId() == null); - assertTrue("found bob child", t.findTreeMember(f.getName()) == f); - - final TreeEntry[] i = t.members(); - assertTrue("iterator is not empty", i.length > 0); - assertTrue("iterator returns file", i[0] == f); - assertEquals("iterator is empty", 1, i.length); - } - - @Test - public void test005_addRecursiveFile() throws IOException { - final Tree t = new Tree(db); - final FileTreeEntry f = t.addFile("a/b/c"); - assertNotNull("created f", f); - assertEquals("c", f.getName()); - assertEquals("b", f.getParent().getName()); - assertEquals("a", f.getParent().getParent().getName()); - assertTrue("t is great-grandparent", t == f.getParent().getParent() - .getParent()); - } - - @Test - public void test005_addRecursiveTree() throws IOException { - final Tree t = new Tree(db); - final Tree f = t.addTree("a/b/c"); - assertNotNull("created f", f); - assertEquals("c", f.getName()); - assertEquals("b", f.getParent().getName()); - assertEquals("a", f.getParent().getParent().getName()); - assertTrue("t is great-grandparent", t == f.getParent().getParent() - .getParent()); - } - - @Test - public void test006_addDeepTree() throws IOException { - final Tree t = new Tree(db); - - final Tree e = t.addTree("e"); - assertNotNull("have e", e); - assertTrue("e.parent == t", e.getParent() == t); - final Tree f = t.addTree("f"); - assertNotNull("have f", f); - assertTrue("f.parent == t", f.getParent() == t); - final Tree g = f.addTree("g"); - assertNotNull("have g", g); - assertTrue("g.parent == f", g.getParent() == f); - final Tree h = g.addTree("h"); - assertNotNull("have h", h); - assertTrue("h.parent = g", h.getParent() == g); - - h.setId(SOME_FAKE_ID); - assertTrue("h not modified", !h.isModified()); - g.setId(SOME_FAKE_ID); - assertTrue("g not modified", !g.isModified()); - f.setId(SOME_FAKE_ID); - assertTrue("f not modified", !f.isModified()); - e.setId(SOME_FAKE_ID); - assertTrue("e not modified", !e.isModified()); - t.setId(SOME_FAKE_ID); - assertTrue("t not modified.", !t.isModified()); - - assertEquals("full path of h ok", "f/g/h", h.getFullName()); - assertTrue("Can find h", t.findTreeMember(h.getFullName()) == h); - assertTrue("Can't find f/z", t.findBlobMember("f/z") == null); - assertTrue("Can't find y/z", t.findBlobMember("y/z") == null); - - final FileTreeEntry i = h.addFile("i"); - assertNotNull(i); - assertEquals("full path of i ok", "f/g/h/i", i.getFullName()); - assertTrue("Can find i", t.findBlobMember(i.getFullName()) == i); - assertTrue("h modified", h.isModified()); - assertTrue("g modified", g.isModified()); - assertTrue("f modified", f.isModified()); - assertTrue("e not modified", !e.isModified()); - assertTrue("t modified", t.isModified()); - - assertTrue("h no id", h.getId() == null); - assertTrue("g no id", g.getId() == null); - assertTrue("f no id", f.getId() == null); - assertTrue("e has id", e.getId() != null); - assertTrue("t no id", t.getId() == null); - } - - @Test - public void test007_manyFileLookup() throws IOException { - final Tree t = new Tree(db); - final List files = new ArrayList(26 * 26); - for (char level1 = 'a'; level1 <= 'z'; level1++) { - for (char level2 = 'a'; level2 <= 'z'; level2++) { - final String n = "." + level1 + level2 + "9"; - final FileTreeEntry f = t.addFile(n); - assertNotNull("File " + n + " added.", f); - assertEquals(n, f.getName()); - files.add(f); - } - } - assertEquals(files.size(), t.memberCount()); - final TreeEntry[] ents = t.members(); - assertNotNull(ents); - assertEquals(files.size(), ents.length); - for (int k = 0; k < ents.length; k++) { - assertTrue("File " + files.get(k).getName() - + " is at " + k + ".", files.get(k) == ents[k]); - } - } - - @Test - public void test008_SubtreeInternalSorting() throws IOException { - final Tree t = new Tree(db); - final FileTreeEntry e0 = t.addFile("a-b"); - final FileTreeEntry e1 = t.addFile("a-"); - final FileTreeEntry e2 = t.addFile("a=b"); - final Tree e3 = t.addTree("a"); - final FileTreeEntry e4 = t.addFile("a="); - - final TreeEntry[] ents = t.members(); - assertSame(e1, ents[0]); - assertSame(e0, ents[1]); - assertSame(e3, ents[2]); - assertSame(e4, ents[3]); - assertSame(e2, ents[4]); - } - - @Test - public void test009_SymlinkAndGitlink() throws IOException { - final Tree symlinkTree = mapTree("symlink"); - assertTrue("Symlink entry exists", symlinkTree.existsBlob("symlink.txt")); - final Tree gitlinkTree = mapTree("gitlink"); - assertTrue("Gitlink entry exists", gitlinkTree.existsBlob("submodule")); - } - - private Tree mapTree(String name) throws IOException { - ObjectId id = db.resolve(name + "^{tree}"); - return new Tree(db, id, db.open(id).getCachedBytes()); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java index 2a59f58c6..9c9edc147 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java @@ -47,11 +47,10 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.FileTreeEntry; +import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.lib.TreeFormatter; import org.junit.Test; @SuppressWarnings("deprecation") @@ -220,28 +219,24 @@ public void testEmptyTreeCorruption() throws Exception { .fromString("abbbfafe3129f85747aba7bfac992af77134c607"); final RevTree tree_root, tree_A, tree_AB; final RevCommit b; - { - Tree root = new Tree(db); - Tree A = root.addTree("A"); - FileTreeEntry B = root.addFile("B"); - B.setId(bId); + try (ObjectInserter inserter = db.newObjectInserter()) { + ObjectId empty = inserter.insert(new TreeFormatter()); - Tree A_A = A.addTree("A"); - Tree A_B = A.addTree("B"); + TreeFormatter A = new TreeFormatter(); + A.append("A", FileMode.TREE, empty); + A.append("B", FileMode.TREE, empty); + ObjectId idA = inserter.insert(A); - try (final ObjectInserter inserter = db.newObjectInserter()) { - A_A.setId(inserter.insert(Constants.OBJ_TREE, A_A.format())); - A_B.setId(inserter.insert(Constants.OBJ_TREE, A_B.format())); - A.setId(inserter.insert(Constants.OBJ_TREE, A.format())); - root.setId(inserter.insert(Constants.OBJ_TREE, root.format())); - inserter.flush(); - } + TreeFormatter root = new TreeFormatter(); + root.append("A", FileMode.TREE, idA); + root.append("B", FileMode.REGULAR_FILE, bId); + ObjectId idRoot = inserter.insert(root); + inserter.flush(); - tree_root = rw.parseTree(root.getId()); - tree_A = rw.parseTree(A.getId()); - tree_AB = rw.parseTree(A_A.getId()); - assertSame(tree_AB, rw.parseTree(A_B.getId())); - b = commit(rw.parseTree(root.getId())); + tree_root = objw.parseTree(idRoot); + tree_A = objw.parseTree(idA); + tree_AB = objw.parseTree(empty); + b = commit(tree_root); } markStart(b); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java index aca7c80fd..c3ff7df8f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java @@ -44,7 +44,6 @@ package org.eclipse.jgit.treewalk; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; -import static org.eclipse.jgit.lib.Constants.OBJ_TREE; import static org.eclipse.jgit.lib.Constants.encode; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -54,11 +53,10 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.lib.TreeFormatter; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.junit.Test; -@SuppressWarnings("deprecation") public class TreeWalkBasicDiffTest extends RepositoryTestCase { @Test public void testMissingSubtree_DetectFileAdded_FileModified() @@ -72,62 +70,63 @@ public void testMissingSubtree_DetectFileAdded_FileModified() // Create sub-a/empty, sub-c/empty = hello. { - final Tree root = new Tree(db); + TreeFormatter root = new TreeFormatter(); { - final Tree subA = root.addTree("sub-a"); - subA.addFile("empty").setId(aFileId); - subA.setId(inserter.insert(OBJ_TREE, subA.format())); + TreeFormatter subA = new TreeFormatter(); + subA.append("empty", FileMode.REGULAR_FILE, aFileId); + root.append("sub-a", FileMode.TREE, inserter.insert(subA)); } { - final Tree subC = root.addTree("sub-c"); - subC.addFile("empty").setId(cFileId1); - subC.setId(inserter.insert(OBJ_TREE, subC.format())); + TreeFormatter subC = new TreeFormatter(); + subC.append("empty", FileMode.REGULAR_FILE, cFileId1); + root.append("sub-c", FileMode.TREE, inserter.insert(subC)); } - oldTree = inserter.insert(OBJ_TREE, root.format()); + oldTree = inserter.insert(root); } // Create sub-a/empty, sub-b/empty, sub-c/empty. { - final Tree root = new Tree(db); + TreeFormatter root = new TreeFormatter(); { - final Tree subA = root.addTree("sub-a"); - subA.addFile("empty").setId(aFileId); - subA.setId(inserter.insert(OBJ_TREE, subA.format())); + TreeFormatter subA = new TreeFormatter(); + subA.append("empty", FileMode.REGULAR_FILE, aFileId); + root.append("sub-a", FileMode.TREE, inserter.insert(subA)); } { - final Tree subB = root.addTree("sub-b"); - subB.addFile("empty").setId(bFileId); - subB.setId(inserter.insert(OBJ_TREE, subB.format())); + TreeFormatter subB = new TreeFormatter(); + subB.append("empty", FileMode.REGULAR_FILE, bFileId); + root.append("sub-b", FileMode.TREE, inserter.insert(subB)); } { - final Tree subC = root.addTree("sub-c"); - subC.addFile("empty").setId(cFileId2); - subC.setId(inserter.insert(OBJ_TREE, subC.format())); + TreeFormatter subC = new TreeFormatter(); + subC.append("empty", FileMode.REGULAR_FILE, cFileId2); + root.append("sub-c", FileMode.TREE, inserter.insert(subC)); } - newTree = inserter.insert(OBJ_TREE, root.format()); + newTree = inserter.insert(root); } inserter.flush(); } - final TreeWalk tw = new TreeWalk(db); - tw.reset(oldTree, newTree); - tw.setRecursive(true); - tw.setFilter(TreeFilter.ANY_DIFF); + try (TreeWalk tw = new TreeWalk(db)) { + tw.reset(oldTree, newTree); + tw.setRecursive(true); + tw.setFilter(TreeFilter.ANY_DIFF); - assertTrue(tw.next()); - assertEquals("sub-b/empty", tw.getPathString()); - assertEquals(FileMode.MISSING, tw.getFileMode(0)); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); - assertEquals(ObjectId.zeroId(), tw.getObjectId(0)); - assertEquals(bFileId, tw.getObjectId(1)); + assertTrue(tw.next()); + assertEquals("sub-b/empty", tw.getPathString()); + assertEquals(FileMode.MISSING, tw.getFileMode(0)); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); + assertEquals(ObjectId.zeroId(), tw.getObjectId(0)); + assertEquals(bFileId, tw.getObjectId(1)); - assertTrue(tw.next()); - assertEquals("sub-c/empty", tw.getPathString()); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0)); - assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); - assertEquals(cFileId1, tw.getObjectId(0)); - assertEquals(cFileId2, tw.getObjectId(1)); + assertTrue(tw.next()); + assertEquals("sub-c/empty", tw.getPathString()); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0)); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); + assertEquals(cFileId1, tw.getObjectId(0)); + assertEquals(cFileId2, tw.getObjectId(1)); - assertFalse(tw.next()); + assertFalse(tw.next()); + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java deleted file mode 100644 index 9c689440b..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.IOException; - -/** - * A representation of a file (blob) object in a {@link Tree}. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - * @noreference This class is not intended to be referenced by clients. - * @noextend This class is not intended to be subclassed by clients. - */ -@Deprecated -public class FileTreeEntry extends TreeEntry { - private FileMode mode; - - /** - * Constructor for a File (blob) object. - * - * @param parent - * The {@link Tree} holding this object (or null) - * @param id - * the SHA-1 of the blob (or null for a yet unhashed file) - * @param nameUTF8 - * raw object name in the parent tree - * @param execute - * true if the executable flag is set - */ - public FileTreeEntry(final Tree parent, final ObjectId id, - final byte[] nameUTF8, final boolean execute) { - super(parent, id, nameUTF8); - setExecutable(execute); - } - - public FileMode getMode() { - return mode; - } - - /** - * @return true if this file is executable - */ - public boolean isExecutable() { - return getMode().equals(FileMode.EXECUTABLE_FILE); - } - - /** - * @param execute set/reset the executable flag - */ - public void setExecutable(final boolean execute) { - mode = execute ? FileMode.EXECUTABLE_FILE : FileMode.REGULAR_FILE; - } - - /** - * @return an {@link ObjectLoader} that will return the data - * @throws IOException - */ - public ObjectLoader openReader() throws IOException { - return getRepository().open(getId(), Constants.OBJ_BLOB); - } - - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(' '); - r.append(isExecutable() ? 'X' : 'F'); - r.append(' '); - r.append(getFullName()); - return r.toString(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java deleted file mode 100644 index f42f7a221..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2009, Jonas Fonseca - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -/** - * A tree entry representing a gitlink entry used for submodules. - * - * Note. Java cannot really handle these as file system objects. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - * @noreference This class is not intended to be referenced by clients. - * @noextend This class is not intended to be subclassed by clients. - */ -@Deprecated -public class GitlinkTreeEntry extends TreeEntry { - - /** - * Construct a {@link GitlinkTreeEntry} with the specified name and SHA-1 in - * the specified parent - * - * @param parent - * @param id - * @param nameUTF8 - */ - public GitlinkTreeEntry(final Tree parent, final ObjectId id, - final byte[] nameUTF8) { - super(parent, id, nameUTF8); - } - - public FileMode getMode() { - return FileMode.GITLINK; - } - - @Override - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(" G "); //$NON-NLS-1$ - r.append(getFullName()); - return r.toString(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java deleted file mode 100644 index 5ff83260d..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2006-2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -/** - * A tree entry representing a symbolic link. - * - * Note. Java cannot really handle these as file system objects. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - * @noreference This class is not intended to be referenced by clients. - * @noextend This class is not intended to be subclassed by clients. - */ -@Deprecated -public class SymlinkTreeEntry extends TreeEntry { - - /** - * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in - * the specified parent - * - * @param parent - * @param id - * @param nameUTF8 - */ - public SymlinkTreeEntry(final Tree parent, final ObjectId id, - final byte[] nameUTF8) { - super(parent, id, nameUTF8); - } - - public FileMode getMode() { - return FileMode.SYMLINK; - } - - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(" S "); //$NON-NLS-1$ - r.append(getFullName()); - return r.toString(); - } -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java deleted file mode 100644 index 94d14401d..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java +++ /dev/null @@ -1,603 +0,0 @@ -/* - * Copyright (C) 2007, Robin Rosenberg - * Copyright (C) 2007-2008, Robin Rosenberg - * Copyright (C) 2006-2008, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.IOException; -import java.text.MessageFormat; - -import org.eclipse.jgit.errors.CorruptObjectException; -import org.eclipse.jgit.errors.EntryExistsException; -import org.eclipse.jgit.errors.MissingObjectException; -import org.eclipse.jgit.errors.ObjectWritingException; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.util.RawParseUtils; - -/** - * A representation of a Git tree entry. A Tree is a directory in Git. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - * @noreference This class is not intended to be referenced by clients. - * @noextend This class is not intended to be subclassed by clients. - */ -@Deprecated -public class Tree extends TreeEntry { - private static final TreeEntry[] EMPTY_TREE = {}; - - /** - * Compare two names represented as bytes. Since git treats names of trees and - * blobs differently we have one parameter that represents a '/' for trees. For - * other objects the value should be NUL. The names are compare by their positive - * byte value (0..255). - * - * A blob and a tree with the same name will not compare equal. - * - * @param a name - * @param b name - * @param lasta '/' if a is a tree, else NUL - * @param lastb '/' if b is a tree, else NUL - * - * @return < 0 if a is sorted before b, 0 if they are the same, else b - */ - public static final int compareNames(final byte[] a, final byte[] b, final int lasta,final int lastb) { - return compareNames(a, b, 0, b.length, lasta, lastb); - } - - private static final int compareNames(final byte[] a, final byte[] nameUTF8, - final int nameStart, final int nameEnd, final int lasta, int lastb) { - int j,k; - for (j = 0, k = nameStart; j < a.length && k < nameEnd; j++, k++) { - final int aj = a[j] & 0xff; - final int bk = nameUTF8[k] & 0xff; - if (aj < bk) - return -1; - else if (aj > bk) - return 1; - } - if (j < a.length) { - int aj = a[j]&0xff; - if (aj < lastb) - return -1; - else if (aj > lastb) - return 1; - else - if (j == a.length - 1) - return 0; - else - return -1; - } - if (k < nameEnd) { - int bk = nameUTF8[k] & 0xff; - if (lasta < bk) - return -1; - else if (lasta > bk) - return 1; - else - if (k == nameEnd - 1) - return 0; - else - return 1; - } - if (lasta < lastb) - return -1; - else if (lasta > lastb) - return 1; - - final int namelength = nameEnd - nameStart; - if (a.length == namelength) - return 0; - else if (a.length < namelength) - return -1; - else - return 1; - } - - private static final byte[] substring(final byte[] s, final int nameStart, - final int nameEnd) { - if (nameStart == 0 && nameStart == s.length) - return s; - final byte[] n = new byte[nameEnd - nameStart]; - System.arraycopy(s, nameStart, n, 0, n.length); - return n; - } - - private static final int binarySearch(final TreeEntry[] entries, - final byte[] nameUTF8, final int nameUTF8last, final int nameStart, final int nameEnd) { - if (entries.length == 0) - return -1; - int high = entries.length; - int low = 0; - do { - final int mid = (low + high) >>> 1; - final int cmp = compareNames(entries[mid].getNameUTF8(), nameUTF8, - nameStart, nameEnd, TreeEntry.lastChar(entries[mid]), nameUTF8last); - if (cmp < 0) - low = mid + 1; - else if (cmp == 0) - return mid; - else - high = mid; - } while (low < high); - return -(low + 1); - } - - private final Repository db; - - private TreeEntry[] contents; - - /** - * Constructor for a new Tree - * - * @param repo The repository that owns the Tree. - */ - public Tree(final Repository repo) { - super(null, null, null); - db = repo; - contents = EMPTY_TREE; - } - - /** - * Construct a Tree object with known content and hash value - * - * @param repo - * @param myId - * @param raw - * @throws IOException - */ - public Tree(final Repository repo, final ObjectId myId, final byte[] raw) - throws IOException { - super(null, myId, null); - db = repo; - readTree(raw); - } - - /** - * Construct a new Tree under another Tree - * - * @param parent - * @param nameUTF8 - */ - public Tree(final Tree parent, final byte[] nameUTF8) { - super(parent, null, nameUTF8); - db = parent.getRepository(); - contents = EMPTY_TREE; - } - - /** - * Construct a Tree with a known SHA-1 under another tree. Data is not yet - * specified and will have to be loaded on demand. - * - * @param parent - * @param id - * @param nameUTF8 - */ - public Tree(final Tree parent, final ObjectId id, final byte[] nameUTF8) { - super(parent, id, nameUTF8); - db = parent.getRepository(); - } - - public FileMode getMode() { - return FileMode.TREE; - } - - /** - * @return true if this Tree is the top level Tree. - */ - public boolean isRoot() { - return getParent() == null; - } - - public Repository getRepository() { - return db; - } - - /** - * @return true of the data of this Tree is loaded - */ - public boolean isLoaded() { - return contents != null; - } - - /** - * Forget the in-memory data for this tree. - */ - public void unload() { - if (isModified()) - throw new IllegalStateException(JGitText.get().cannotUnloadAModifiedTree); - contents = null; - } - - /** - * Adds a new or existing file with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param name Name - * @return a {@link FileTreeEntry} for the added file. - * @throws IOException - */ - public FileTreeEntry addFile(final String name) throws IOException { - return addFile(Repository.gitInternalSlash(Constants.encode(name)), 0); - } - - /** - * Adds a new or existing file with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param s an array containing the name - * @param offset when the name starts in the tree. - * - * @return a {@link FileTreeEntry} for the added file. - * @throws IOException - */ - public FileTreeEntry addFile(final byte[] s, final int offset) - throws IOException { - int slash; - int p; - - for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { - // search for path component terminator - } - - ensureLoaded(); - byte xlast = slash= 0 && slash < s.length && contents[p] instanceof Tree) - return ((Tree) contents[p]).addFile(s, slash + 1); - - final byte[] newName = substring(s, offset, slash); - if (p >= 0) - throw new EntryExistsException(RawParseUtils.decode(newName)); - else if (slash < s.length) { - final Tree t = new Tree(this, newName); - insertEntry(p, t); - return t.addFile(s, slash + 1); - } else { - final FileTreeEntry f = new FileTreeEntry(this, null, newName, - false); - insertEntry(p, f); - return f; - } - } - - /** - * Adds a new or existing Tree with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param name Name - * @return a {@link FileTreeEntry} for the added tree. - * @throws IOException - */ - public Tree addTree(final String name) throws IOException { - return addTree(Repository.gitInternalSlash(Constants.encode(name)), 0); - } - - /** - * Adds a new or existing Tree with the specified name to this tree. - * Trees are added if necessary as the name may contain '/':s. - * - * @param s an array containing the name - * @param offset when the name starts in the tree. - * - * @return a {@link FileTreeEntry} for the added tree. - * @throws IOException - */ - public Tree addTree(final byte[] s, final int offset) throws IOException { - int slash; - int p; - - for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { - // search for path component terminator - } - - ensureLoaded(); - p = binarySearch(contents, s, (byte)'/', offset, slash); - if (p >= 0 && slash < s.length && contents[p] instanceof Tree) - return ((Tree) contents[p]).addTree(s, slash + 1); - - final byte[] newName = substring(s, offset, slash); - if (p >= 0) - throw new EntryExistsException(RawParseUtils.decode(newName)); - - final Tree t = new Tree(this, newName); - insertEntry(p, t); - return slash == s.length ? t : t.addTree(s, slash + 1); - } - - /** - * Add the specified tree entry to this tree. - * - * @param e - * @throws IOException - */ - public void addEntry(final TreeEntry e) throws IOException { - final int p; - - ensureLoaded(); - p = binarySearch(contents, e.getNameUTF8(), TreeEntry.lastChar(e), 0, e.getNameUTF8().length); - if (p < 0) { - e.attachParent(this); - insertEntry(p, e); - } else { - throw new EntryExistsException(e.getName()); - } - } - - private void insertEntry(int p, final TreeEntry e) { - final TreeEntry[] c = contents; - final TreeEntry[] n = new TreeEntry[c.length + 1]; - p = -(p + 1); - for (int k = c.length - 1; k >= p; k--) - n[k + 1] = c[k]; - n[p] = e; - for (int k = p - 1; k >= 0; k--) - n[k] = c[k]; - contents = n; - setModified(); - } - - void removeEntry(final TreeEntry e) { - final TreeEntry[] c = contents; - final int p = binarySearch(c, e.getNameUTF8(), TreeEntry.lastChar(e), 0, - e.getNameUTF8().length); - if (p >= 0) { - final TreeEntry[] n = new TreeEntry[c.length - 1]; - for (int k = c.length - 1; k > p; k--) - n[k - 1] = c[k]; - for (int k = p - 1; k >= 0; k--) - n[k] = c[k]; - contents = n; - setModified(); - } - } - - /** - * @return number of members in this tree - * @throws IOException - */ - public int memberCount() throws IOException { - ensureLoaded(); - return contents.length; - } - - /** - * Return all members of the tree sorted in Git order. - * - * Entries are sorted by the numerical unsigned byte - * values with (sub)trees having an implicit '/'. An - * example of a tree with three entries. a:b is an - * actual file name here. - * - *

- * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a.b - * 040000 tree 4277b6e69d25e5efa77c455340557b384a4c018a a - * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a:b - * - * @return all entries in this Tree, sorted. - * @throws IOException - */ - public TreeEntry[] members() throws IOException { - ensureLoaded(); - final TreeEntry[] c = contents; - if (c.length != 0) { - final TreeEntry[] r = new TreeEntry[c.length]; - for (int k = c.length - 1; k >= 0; k--) - r[k] = c[k]; - return r; - } else - return c; - } - - private boolean exists(final String s, byte slast) throws IOException { - return findMember(s, slast) != null; - } - - /** - * @param path to the tree. - * @return true if a tree with the specified path can be found under this - * tree. - * @throws IOException - */ - public boolean existsTree(String path) throws IOException { - return exists(path,(byte)'/'); - } - - /** - * @param path of the non-tree entry. - * @return true if a blob, symlink, or gitlink with the specified name - * can be found under this tree. - * @throws IOException - */ - public boolean existsBlob(String path) throws IOException { - return exists(path,(byte)0); - } - - private TreeEntry findMember(final String s, byte slast) throws IOException { - return findMember(Repository.gitInternalSlash(Constants.encode(s)), slast, 0); - } - - private TreeEntry findMember(final byte[] s, final byte slast, final int offset) - throws IOException { - int slash; - int p; - - for (slash = offset; slash < s.length && s[slash] != '/'; slash++) { - // search for path component terminator - } - - ensureLoaded(); - byte xlast = slash= 0) { - final TreeEntry r = contents[p]; - if (slash < s.length-1) - return r instanceof Tree ? ((Tree) r).findMember(s, slast, slash + 1) - : null; - return r; - } - return null; - } - - /** - * @param s - * blob name - * @return a {@link TreeEntry} representing an object with the specified - * relative path. - * @throws IOException - */ - public TreeEntry findBlobMember(String s) throws IOException { - return findMember(s,(byte)0); - } - - /** - * @param s Tree Name - * @return a Tree with the name s or null - * @throws IOException - */ - public TreeEntry findTreeMember(String s) throws IOException { - return findMember(s,(byte)'/'); - } - - private void ensureLoaded() throws IOException, MissingObjectException { - if (!isLoaded()) { - ObjectLoader ldr = db.open(getId(), Constants.OBJ_TREE); - readTree(ldr.getCachedBytes()); - } - } - - private void readTree(final byte[] raw) throws IOException { - final int rawSize = raw.length; - int rawPtr = 0; - TreeEntry[] temp; - int nextIndex = 0; - - while (rawPtr < rawSize) { - while (rawPtr < rawSize && raw[rawPtr] != 0) - rawPtr++; - rawPtr++; - rawPtr += Constants.OBJECT_ID_LENGTH; - nextIndex++; - } - - temp = new TreeEntry[nextIndex]; - rawPtr = 0; - nextIndex = 0; - while (rawPtr < rawSize) { - int c = raw[rawPtr++]; - if (c < '0' || c > '7') - throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidEntryMode); - int mode = c - '0'; - for (;;) { - c = raw[rawPtr++]; - if (' ' == c) - break; - else if (c < '0' || c > '7') - throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidMode); - mode <<= 3; - mode += c - '0'; - } - - int nameLen = 0; - while (raw[rawPtr + nameLen] != 0) - nameLen++; - final byte[] name = new byte[nameLen]; - System.arraycopy(raw, rawPtr, name, 0, nameLen); - rawPtr += nameLen + 1; - - final ObjectId id = ObjectId.fromRaw(raw, rawPtr); - rawPtr += Constants.OBJECT_ID_LENGTH; - - final TreeEntry ent; - if (FileMode.REGULAR_FILE.equals(mode)) - ent = new FileTreeEntry(this, id, name, false); - else if (FileMode.EXECUTABLE_FILE.equals(mode)) - ent = new FileTreeEntry(this, id, name, true); - else if (FileMode.TREE.equals(mode)) - ent = new Tree(this, id, name); - else if (FileMode.SYMLINK.equals(mode)) - ent = new SymlinkTreeEntry(this, id, name); - else if (FileMode.GITLINK.equals(mode)) - ent = new GitlinkTreeEntry(this, id, name); - else - throw new CorruptObjectException(getId(), MessageFormat.format( - JGitText.get().corruptObjectInvalidMode2, Integer.toOctalString(mode))); - temp[nextIndex++] = ent; - } - - contents = temp; - } - - /** - * Format this Tree in canonical format. - * - * @return canonical encoding of the tree object. - * @throws IOException - * the tree cannot be loaded, or its not in a writable state. - */ - public byte[] format() throws IOException { - TreeFormatter fmt = new TreeFormatter(); - for (TreeEntry e : members()) { - ObjectId id = e.getId(); - if (id == null) - throw new ObjectWritingException(MessageFormat.format(JGitText - .get().objectAtPathDoesNotHaveId, e.getFullName())); - - fmt.append(e.getNameUTF8(), e.getMode(), id); - } - return fmt.toByteArray(); - } - - public String toString() { - final StringBuilder r = new StringBuilder(); - r.append(ObjectId.toString(getId())); - r.append(" T "); //$NON-NLS-1$ - r.append(getFullName()); - return r.toString(); - } - -} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java deleted file mode 100644 index 83be44cf3..000000000 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2007-2008, Robin Rosenberg - * Copyright (C) 2006-2007, Shawn O. Pearce - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.eclipse.jgit.lib; - -import java.io.IOException; - -import org.eclipse.jgit.util.RawParseUtils; - -/** - * This class represents an entry in a tree, like a blob or another tree. - * - * @deprecated To look up information about a single path, use - * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}. - * To lookup information about multiple paths at once, use a - * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's - * information from its getter methods. - * @noreference This class is not intended to be referenced by clients. - * @noextend This class is not intended to be subclassed by clients. - */ -@Deprecated -public abstract class TreeEntry implements Comparable { - private byte[] nameUTF8; - - private Tree parent; - - private ObjectId id; - - /** - * Construct a named tree entry. - * - * @param myParent - * @param myId - * @param myNameUTF8 - */ - protected TreeEntry(final Tree myParent, final ObjectId myId, - final byte[] myNameUTF8) { - nameUTF8 = myNameUTF8; - parent = myParent; - id = myId; - } - - /** - * @return parent of this tree. - */ - public Tree getParent() { - return parent; - } - - /** - * Delete this entry. - */ - public void delete() { - getParent().removeEntry(this); - detachParent(); - } - - /** - * Detach this entry from it's parent. - */ - public void detachParent() { - parent = null; - } - - void attachParent(final Tree p) { - parent = p; - } - - /** - * @return the repository owning this entry. - */ - public Repository getRepository() { - return getParent().getRepository(); - } - - /** - * @return the raw byte name of this entry. - */ - public byte[] getNameUTF8() { - return nameUTF8; - } - - /** - * @return the name of this entry. - */ - public String getName() { - if (nameUTF8 != null) - return RawParseUtils.decode(nameUTF8); - return null; - } - - /** - * Rename this entry. - * - * @param n The new name - * @throws IOException - */ - public void rename(final String n) throws IOException { - rename(Constants.encode(n)); - } - - /** - * Rename this entry. - * - * @param n The new name - * @throws IOException - */ - public void rename(final byte[] n) throws IOException { - final Tree t = getParent(); - if (t != null) { - delete(); - } - nameUTF8 = n; - if (t != null) { - t.addEntry(this); - } - } - - /** - * @return true if this entry is new or modified since being loaded. - */ - public boolean isModified() { - return getId() == null; - } - - /** - * Mark this entry as modified. - */ - public void setModified() { - setId(null); - } - - /** - * @return SHA-1 of this tree entry (null for new unhashed entries) - */ - public ObjectId getId() { - return id; - } - - /** - * Set (update) the SHA-1 of this entry. Invalidates the id's of all - * entries above this entry as they will have to be recomputed. - * - * @param n SHA-1 for this entry. - */ - public void setId(final ObjectId n) { - // If we have a parent and our id is being cleared or changed then force - // the parent's id to become unset as it depends on our id. - // - final Tree p = getParent(); - if (p != null && id != n) { - if ((id == null && n != null) || (id != null && n == null) - || !id.equals(n)) { - p.setId(null); - } - } - - id = n; - } - - /** - * @return repository relative name of this entry - */ - public String getFullName() { - final StringBuilder r = new StringBuilder(); - appendFullName(r); - return r.toString(); - } - - /** - * @return repository relative name of the entry - * FIXME better encoding - */ - public byte[] getFullNameUTF8() { - return getFullName().getBytes(); - } - - public int compareTo(final Object o) { - if (this == o) - return 0; - if (o instanceof TreeEntry) - return Tree.compareNames(nameUTF8, ((TreeEntry) o).nameUTF8, lastChar(this), lastChar((TreeEntry)o)); - return -1; - } - - /** - * Helper for accessing tree/blob methods. - * - * @param treeEntry - * @return '/' for Tree entries and NUL for non-treeish objects. - */ - final public static int lastChar(TreeEntry treeEntry) { - if (!(treeEntry instanceof Tree)) - return '\0'; - else - return '/'; - } - - /** - * @return mode (type of object) - */ - public abstract FileMode getMode(); - - private void appendFullName(final StringBuilder r) { - final TreeEntry p = getParent(); - final String n = getName(); - if (p != null) { - p.appendFullName(r); - if (r.length() > 0) { - r.append('/'); - } - } - if (n != null) { - r.append(n); - } - } -} From 462017e02b01582041e26afb7edae60655a1f99d Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Mon, 11 Jan 2016 16:07:24 -0800 Subject: [PATCH 83/89] Ignore API errors about Tree, TreeEntry, FileTreeEntry and friends being removed Bug: 486105 Change-Id: I04adcdb68bee7d5f608bb7ab959fe36a890f9ecd --- org.eclipse.jgit/.settings/.api_filters | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index b2a8f677f..36041f814 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -1,5 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 6b662af76ef66cd75502ace714302d3a36129ed1 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Tue, 19 Jan 2016 13:08:38 -0800 Subject: [PATCH 84/89] Transport: Implement AutoCloseable After creating a Transport instance callers should always call its close() method. Use AutoCloseable to document this idiom and allow use of try-with-resources. Change-Id: I0c6ff3e39ebecdd7a028dbcae1856a818937b186 --- .../http/test/DumbClientDumbServerTest.java | 25 +--- .../jgit/http/test/HttpClientTests.java | 118 +++++------------- .../http/test/SmartClientSmartServerTest.java | 58 ++------- .../jgit/transport/AtomicPushTest.java | 15 +-- .../jgit/transport/BundleWriterTest.java | 6 +- .../ReceivePackAdvertiseRefsHookTest.java | 18 +-- .../eclipse/jgit/transport/TransportTest.java | 73 +++++------ .../org/eclipse/jgit/api/FetchCommand.java | 25 ++-- .../org/eclipse/jgit/api/LsRemoteCommand.java | 42 +++---- .../eclipse/jgit/transport/Connection.java | 7 +- .../org/eclipse/jgit/transport/Transport.java | 6 +- 11 files changed, 131 insertions(+), 262 deletions(-) diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java index 362a09d64..677132d73 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java @@ -140,8 +140,7 @@ public void testListRemote() throws IOException { assertEquals("http", remoteURI.getScheme()); Map map; - Transport t = Transport.open(dst, remoteURI); - try { + try (Transport t = Transport.open(dst, remoteURI)) { // I didn't make up these public interface names, I just // approved them for inclusion into the code base. Sorry. // --spearce @@ -149,14 +148,9 @@ public void testListRemote() throws IOException { assertTrue("isa TransportHttp", t instanceof TransportHttp); assertTrue("isa HttpTransport", t instanceof HttpTransport); - FetchConnection c = t.openFetch(); - try { + try (FetchConnection c = t.openFetch()) { map = c.getRefsMap(); - } finally { - c.close(); } - } finally { - t.close(); } assertNotNull("have map of refs", map); @@ -201,11 +195,8 @@ public void testInitialClone_Loose() throws Exception { Repository dst = createBareRepository(); assertFalse(dst.hasObject(A_txt)); - Transport t = Transport.open(dst, remoteURI); - try { + try (Transport t = Transport.open(dst, remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertTrue(dst.hasObject(A_txt)); @@ -226,11 +217,8 @@ public void testInitialClone_Packed() throws Exception { Repository dst = createBareRepository(); assertFalse(dst.hasObject(A_txt)); - Transport t = Transport.open(dst, remoteURI); - try { + try (Transport t = Transport.open(dst, remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertTrue(dst.hasObject(A_txt)); @@ -265,8 +253,7 @@ public void testPushNotSupported() throws Exception { final RevCommit Q = src.commit().create(); final Repository db = src.getRepository(); - Transport t = Transport.open(db, remoteURI); - try { + try (Transport t = Transport.open(db, remoteURI)) { try { t.push(NullProgressMonitor.INSTANCE, push(src, Q)); fail("push incorrectly completed against a dumb server"); @@ -274,8 +261,6 @@ public void testPushNotSupported() throws Exception { String exp = "remote does not support smart HTTP push"; assertEquals(exp, nse.getMessage()); } - } finally { - t.close(); } } } diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java index cf20898b3..ce7844278 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java @@ -157,8 +157,7 @@ private static String nameOf(final Repository db) { public void testRepositoryNotFound_Dumb() throws Exception { URIish uri = toURIish("/dumb.none/not-found"); Repository dst = createBareRepository(); - Transport t = Transport.open(dst, uri); - try { + try (Transport t = Transport.open(dst, uri)) { try { t.openFetch(); fail("connection opened to not found repository"); @@ -167,8 +166,6 @@ public void testRepositoryNotFound_Dumb() throws Exception { + "/info/refs?service=git-upload-pack not found"; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } } @@ -176,8 +173,7 @@ public void testRepositoryNotFound_Dumb() throws Exception { public void testRepositoryNotFound_Smart() throws Exception { URIish uri = toURIish("/smart.none/not-found"); Repository dst = createBareRepository(); - Transport t = Transport.open(dst, uri); - try { + try (Transport t = Transport.open(dst, uri)) { try { t.openFetch(); fail("connection opened to not found repository"); @@ -186,8 +182,6 @@ public void testRepositoryNotFound_Smart() throws Exception { + "/info/refs?service=git-upload-pack not found"; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } } @@ -201,16 +195,9 @@ public void testListRemote_Dumb_DetachedHEAD() throws Exception { Repository dst = createBareRepository(); Ref head; - Transport t = Transport.open(dst, dumbAuthNoneURI); - try { - FetchConnection c = t.openFetch(); - try { - head = c.getRef(Constants.HEAD); - } finally { - c.close(); - } - } finally { - t.close(); + try (Transport t = Transport.open(dst, dumbAuthNoneURI); + FetchConnection c = t.openFetch()) { + head = c.getRef(Constants.HEAD); } assertNotNull("has " + Constants.HEAD, head); assertEquals(Q, head.getObjectId()); @@ -225,16 +212,9 @@ public void testListRemote_Dumb_NoHEAD() throws Exception { Repository dst = createBareRepository(); Ref head; - Transport t = Transport.open(dst, dumbAuthNoneURI); - try { - FetchConnection c = t.openFetch(); - try { - head = c.getRef(Constants.HEAD); - } finally { - c.close(); - } - } finally { - t.close(); + try (Transport t = Transport.open(dst, dumbAuthNoneURI); + FetchConnection c = t.openFetch()) { + head = c.getRef(Constants.HEAD); } assertNull("has no " + Constants.HEAD, head); } @@ -249,16 +229,9 @@ public void testListRemote_Smart_DetachedHEAD() throws Exception { Repository dst = createBareRepository(); Ref head; - Transport t = Transport.open(dst, smartAuthNoneURI); - try { - FetchConnection c = t.openFetch(); - try { - head = c.getRef(Constants.HEAD); - } finally { - c.close(); - } - } finally { - t.close(); + try (Transport t = Transport.open(dst, smartAuthNoneURI); + FetchConnection c = t.openFetch()) { + head = c.getRef(Constants.HEAD); } assertNotNull("has " + Constants.HEAD, head); assertEquals(Q, head.getObjectId()); @@ -268,16 +241,13 @@ public void testListRemote_Smart_DetachedHEAD() throws Exception { public void testListRemote_Smart_WithQueryParameters() throws Exception { URIish myURI = toURIish("/snone/do?r=1&p=test.git"); Repository dst = createBareRepository(); - Transport t = Transport.open(dst, myURI); - try { + try (Transport t = Transport.open(dst, myURI)) { try { t.openFetch(); fail("test did not fail to find repository as expected"); } catch (NoRemoteRepositoryException err) { // expected } - } finally { - t.close(); } List requests = getRequests(); @@ -296,8 +266,7 @@ public void testListRemote_Smart_WithQueryParameters() throws Exception { @Test public void testListRemote_Dumb_NeedsAuth() throws Exception { Repository dst = createBareRepository(); - Transport t = Transport.open(dst, dumbAuthBasicURI); - try { + try (Transport t = Transport.open(dst, dumbAuthBasicURI)) { try { t.openFetch(); fail("connection opened even info/refs needs auth basic"); @@ -306,42 +275,35 @@ public void testListRemote_Dumb_NeedsAuth() throws Exception { + JGitText.get().noCredentialsProvider; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } } @Test public void testListRemote_Dumb_Auth() throws Exception { Repository dst = createBareRepository(); - Transport t = Transport.open(dst, dumbAuthBasicURI); - t.setCredentialsProvider(new UsernamePasswordCredentialsProvider( - AppServer.username, AppServer.password)); - try { - t.openFetch(); - } finally { - t.close(); + try (Transport t = Transport.open(dst, dumbAuthBasicURI)) { + t.setCredentialsProvider(new UsernamePasswordCredentialsProvider( + AppServer.username, AppServer.password)); + t.openFetch().close(); } - t = Transport.open(dst, dumbAuthBasicURI); - t.setCredentialsProvider(new UsernamePasswordCredentialsProvider( - AppServer.username, "")); - try { - t.openFetch(); - fail("connection opened even info/refs needs auth basic and we provide wrong password"); - } catch (TransportException err) { - String exp = dumbAuthBasicURI + ": " - + JGitText.get().notAuthorized; - assertEquals(exp, err.getMessage()); - } finally { - t.close(); + try (Transport t = Transport.open(dst, dumbAuthBasicURI)) { + t.setCredentialsProvider(new UsernamePasswordCredentialsProvider( + AppServer.username, "")); + try { + t.openFetch(); + fail("connection opened even info/refs needs auth basic and we provide wrong password"); + } catch (TransportException err) { + String exp = dumbAuthBasicURI + ": " + + JGitText.get().notAuthorized; + assertEquals(exp, err.getMessage()); + } } } @Test public void testListRemote_Smart_UploadPackNeedsAuth() throws Exception { Repository dst = createBareRepository(); - Transport t = Transport.open(dst, smartAuthBasicURI); - try { + try (Transport t = Transport.open(dst, smartAuthBasicURI)) { try { t.openFetch(); fail("connection opened even though service disabled"); @@ -350,8 +312,6 @@ public void testListRemote_Smart_UploadPackNeedsAuth() throws Exception { + JGitText.get().noCredentialsProvider; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } } @@ -363,8 +323,7 @@ public void testListRemote_Smart_UploadPackDisabled() throws Exception { cfg.save(); Repository dst = createBareRepository(); - Transport t = Transport.open(dst, smartAuthNoneURI); - try { + try (Transport t = Transport.open(dst, smartAuthNoneURI)) { try { t.openFetch(); fail("connection opened even though service disabled"); @@ -373,24 +332,15 @@ public void testListRemote_Smart_UploadPackDisabled() throws Exception { + JGitText.get().serviceNotEnabledNoName; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } } @Test public void testListRemoteWithoutLocalRepository() throws Exception { - Transport t = Transport.open(smartAuthNoneURI); - try { - FetchConnection c = t.openFetch(); - try { - Ref head = c.getRef(Constants.HEAD); - assertNotNull(head); - } finally { - c.close(); - } - } finally { - t.close(); + try (Transport t = Transport.open(smartAuthNoneURI); + FetchConnection c = t.openFetch()) { + Ref head = c.getRef(Constants.HEAD); + assertNotNull(head); } } } diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java index 9ca0789e2..82861ed9b 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -211,8 +211,7 @@ public void testListRemote() throws IOException { assertEquals("http", remoteURI.getScheme()); Map map; - Transport t = Transport.open(dst, remoteURI); - try { + try (Transport t = Transport.open(dst, remoteURI)) { // I didn't make up these public interface names, I just // approved them for inclusion into the code base. Sorry. // --spearce @@ -226,8 +225,6 @@ public void testListRemote() throws IOException { } finally { c.close(); } - } finally { - t.close(); } assertNotNull("have map of refs", map); @@ -257,8 +254,7 @@ public void testListRemote() throws IOException { public void testListRemote_BadName() throws IOException, URISyntaxException { Repository dst = createBareRepository(); URIish uri = new URIish(this.remoteURI.toString() + ".invalid"); - Transport t = Transport.open(dst, uri); - try { + try (Transport t = Transport.open(dst, uri)) { try { t.openFetch(); fail("fetch connection opened"); @@ -266,8 +262,6 @@ public void testListRemote_BadName() throws IOException, URISyntaxException { assertEquals(uri + ": Git repository not found", notFound.getMessage()); } - } finally { - t.close(); } List requests = getRequests(); @@ -288,11 +282,8 @@ public void testInitialClone_Small() throws Exception { Repository dst = createBareRepository(); assertFalse(dst.hasObject(A_txt)); - Transport t = Transport.open(dst, remoteURI); - try { + try (Transport t = Transport.open(dst, remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertTrue(dst.hasObject(A_txt)); @@ -331,11 +322,8 @@ public void testFetch_FewLocalCommits() throws Exception { // Bootstrap by doing the clone. // TestRepository dst = createTestRepository(); - Transport t = Transport.open(dst.getRepository(), remoteURI); - try { + try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertEquals(B, dst.getRepository().exactRef(master).getObjectId()); List cloneRequests = getRequests(); @@ -352,11 +340,8 @@ public void testFetch_FewLocalCommits() throws Exception { // Now incrementally update. // - t = Transport.open(dst.getRepository(), remoteURI); - try { + try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertEquals(Z, dst.getRepository().exactRef(master).getObjectId()); @@ -394,11 +379,8 @@ public void testFetch_TooManyLocalCommits() throws Exception { // Bootstrap by doing the clone. // TestRepository dst = createTestRepository(); - Transport t = Transport.open(dst.getRepository(), remoteURI); - try { + try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertEquals(B, dst.getRepository().exactRef(master).getObjectId()); List cloneRequests = getRequests(); @@ -418,11 +400,8 @@ public void testFetch_TooManyLocalCommits() throws Exception { // Now incrementally update. // - t = Transport.open(dst.getRepository(), remoteURI); - try { + try (Transport t = Transport.open(dst.getRepository(), remoteURI)) { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); - } finally { - t.close(); } assertEquals(Z, dst.getRepository().exactRef(master).getObjectId()); @@ -474,8 +453,7 @@ public void testInitialClone_BrokenServer() throws Exception { Repository dst = createBareRepository(); assertFalse(dst.hasObject(A_txt)); - Transport t = Transport.open(dst, brokenURI); - try { + try (Transport t = Transport.open(dst, brokenURI)) { try { t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); fail("fetch completed despite upload-pack being broken"); @@ -485,8 +463,6 @@ public void testInitialClone_BrokenServer() throws Exception { + " received Content-Type text/plain; charset=UTF-8"; assertEquals(exp, err.getMessage()); } - } finally { - t.close(); } List requests = getRequests(); @@ -517,12 +493,10 @@ public void testPush_NotAuthorized() throws Exception { final RevCommit Q = src.commit().add("Q", Q_txt).create(); final Repository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; - Transport t; // push anonymous shouldn't be allowed. // - t = Transport.open(db, remoteURI); - try { + try (Transport t = Transport.open(db, remoteURI)) { final String srcExpr = Q.name(); final boolean forceUpdate = false; final String localName = null; @@ -538,8 +512,6 @@ public void testPush_NotAuthorized() throws Exception { + JGitText.get().authenticationNotSupported; assertEquals(exp, e.getMessage()); } - } finally { - t.close(); } List requests = getRequests(); @@ -560,12 +532,10 @@ public void testPush_CreateBranch() throws Exception { final RevCommit Q = src.commit().add("Q", Q_txt).create(); final Repository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; - Transport t; enableReceivePack(); - t = Transport.open(db, remoteURI); - try { + try (Transport t = Transport.open(db, remoteURI)) { final String srcExpr = Q.name(); final boolean forceUpdate = false; final String localName = null; @@ -574,8 +544,6 @@ public void testPush_CreateBranch() throws Exception { RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(), srcExpr, dstName, forceUpdate, localName, oldId); t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u)); - } finally { - t.close(); } assertTrue(remoteRepository.hasObject(Q_txt)); @@ -633,7 +601,6 @@ public void testPush_ChunkedEncoding() throws Exception { final RevCommit Q = src.commit().add("Q", Q_bin).create(); final Repository db = src.getRepository(); final String dstName = Constants.R_HEADS + "new.branch"; - Transport t; enableReceivePack(); @@ -642,8 +609,7 @@ public void testPush_ChunkedEncoding() throws Exception { cfg.setInt("http", null, "postbuffer", 8 * 1024); cfg.save(); - t = Transport.open(db, remoteURI); - try { + try (Transport t = Transport.open(db, remoteURI)) { final String srcExpr = Q.name(); final boolean forceUpdate = false; final String localName = null; @@ -652,8 +618,6 @@ public void testPush_ChunkedEncoding() throws Exception { RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(), srcExpr, dstName, forceUpdate, localName, oldId); t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u)); - } finally { - t.close(); } assertTrue(remoteRepository.hasObject(Q_bin)); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java index 782e414b6..c1e078d10 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java @@ -112,12 +112,9 @@ private static InMemoryRepository newRepo(String name) { public void pushNonAtomic() throws Exception { PushResult r; server.setPerformsAtomicTransactions(false); - Transport tn = testProtocol.open(uri, client, "server"); - try { + try (Transport tn = testProtocol.open(uri, client, "server")) { tn.setPushAtomic(false); r = tn.push(NullProgressMonitor.INSTANCE, commands()); - } finally { - tn.close(); } RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one"); @@ -131,12 +128,9 @@ public void pushNonAtomic() throws Exception { @Test public void pushAtomicClientGivesUpEarly() throws Exception { PushResult r; - Transport tn = testProtocol.open(uri, client, "server"); - try { + try (Transport tn = testProtocol.open(uri, client, "server")) { tn.setPushAtomic(true); r = tn.push(NullProgressMonitor.INSTANCE, commands()); - } finally { - tn.close(); } RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one"); @@ -167,8 +161,7 @@ public void pushAtomicDisabled() throws Exception { ObjectId.zeroId())); server.setPerformsAtomicTransactions(false); - Transport tn = testProtocol.open(uri, client, "server"); - try { + try (Transport tn = testProtocol.open(uri, client, "server")) { tn.setPushAtomic(true); tn.push(NullProgressMonitor.INSTANCE, cmds); fail("did not throw TransportException"); @@ -176,8 +169,6 @@ public void pushAtomicDisabled() throws Exception { assertEquals( uri + ": " + JGitText.get().atomicPushNotSupported, e.getMessage()); - } finally { - tn.close(); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java index ba89d2d61..f94f70725 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java @@ -166,8 +166,10 @@ private static FetchResult fetchFromBundle(final Repository newRepo, final ByteArrayInputStream in = new ByteArrayInputStream(bundle); final RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*"); final Set refs = Collections.singleton(rs); - return new TransportBundleStream(newRepo, uri, in).fetch( - NullProgressMonitor.INSTANCE, refs); + try (TransportBundleStream transport = new TransportBundleStream( + newRepo, uri, in)) { + return transport.fetch(NullProgressMonitor.INSTANCE, refs); + } } private byte[] makeBundle(final String name, diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java index aa5914fe0..94bc383db 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ReceivePackAdvertiseRefsHookTest.java @@ -116,12 +116,9 @@ public void setUp() throws Exception { // Clone from dst into src // - Transport t = Transport.open(src, uriOf(dst)); - try { + try (Transport t = Transport.open(src, uriOf(dst))) { t.fetch(PM, Collections.singleton(new RefSpec("+refs/*:refs/*"))); assertEquals(B, src.resolve(R_MASTER)); - } finally { - t.close(); } // Now put private stuff into dst. @@ -144,7 +141,8 @@ public void tearDown() throws Exception { @Test public void testFilterHidesPrivate() throws Exception { Map refs; - TransportLocal t = new TransportLocal(src, uriOf(dst), dst.getDirectory()) { + try (TransportLocal t = new TransportLocal(src, uriOf(dst), + dst.getDirectory()) { @Override ReceivePack createReceivePack(final Repository db) { db.close(); @@ -154,16 +152,10 @@ ReceivePack createReceivePack(final Repository db) { rp.setAdvertiseRefsHook(new HidePrivateHook()); return rp; } - }; - try { - PushConnection c = t.openPush(); - try { + }) { + try (PushConnection c = t.openPush()) { refs = c.getRefsMap(); - } finally { - c.close(); } - } finally { - t.close(); } assertNotNull(refs); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java index 55e1e4420..5519f61ac 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java @@ -61,13 +61,10 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; -import org.junit.After; import org.junit.Before; import org.junit.Test; public class TransportTest extends SampleDataRepositoryTestCase { - private Transport transport; - private RemoteConfig remoteConfig; @Override @@ -77,17 +74,6 @@ public void setUp() throws Exception { final Config config = db.getConfig(); remoteConfig = new RemoteConfig(config, "test"); remoteConfig.addURI(new URIish("http://everyones.loves.git/u/2")); - transport = null; - } - - @Override - @After - public void tearDown() throws Exception { - if (transport != null) { - transport.close(); - transport = null; - } - super.tearDown(); } /** @@ -99,10 +85,11 @@ public void tearDown() throws Exception { @Test public void testFindRemoteRefUpdatesNoWildcardNoTracking() throws IOException { - transport = Transport.open(db, remoteConfig); - final Collection result = transport - .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( - "refs/heads/master:refs/heads/x"))); + Collection result; + try (Transport transport = Transport.open(db, remoteConfig)) { + result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1, + new RefSpec("refs/heads/master:refs/heads/x"))); + } assertEquals(1, result.size()); final RemoteRefUpdate rru = result.iterator().next(); @@ -122,10 +109,11 @@ public void testFindRemoteRefUpdatesNoWildcardNoTracking() @Test public void testFindRemoteRefUpdatesNoWildcardNoDestination() throws IOException { - transport = Transport.open(db, remoteConfig); - final Collection result = transport - .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( - "+refs/heads/master"))); + Collection result; + try (Transport transport = Transport.open(db, remoteConfig)) { + result = transport.findRemoteRefUpdatesFor( + Collections.nCopies(1, new RefSpec("+refs/heads/master"))); + } assertEquals(1, result.size()); final RemoteRefUpdate rru = result.iterator().next(); @@ -143,10 +131,11 @@ public void testFindRemoteRefUpdatesNoWildcardNoDestination() */ @Test public void testFindRemoteRefUpdatesWildcardNoTracking() throws IOException { - transport = Transport.open(db, remoteConfig); - final Collection result = transport - .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( - "+refs/heads/*:refs/heads/test/*"))); + Collection result; + try (Transport transport = Transport.open(db, remoteConfig)) { + result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1, + new RefSpec("+refs/heads/*:refs/heads/test/*"))); + } assertEquals(12, result.size()); boolean foundA = false; @@ -171,12 +160,14 @@ public void testFindRemoteRefUpdatesWildcardNoTracking() throws IOException { */ @Test public void testFindRemoteRefUpdatesTwoRefSpecs() throws IOException { - transport = Transport.open(db, remoteConfig); final RefSpec specA = new RefSpec("+refs/heads/a:refs/heads/b"); final RefSpec specC = new RefSpec("+refs/heads/c:refs/heads/d"); final Collection specs = Arrays.asList(specA, specC); - final Collection result = transport - .findRemoteRefUpdatesFor(specs); + + Collection result; + try (Transport transport = Transport.open(db, remoteConfig)) { + result = transport.findRemoteRefUpdatesFor(specs); + } assertEquals(2, result.size()); boolean foundA = false; @@ -202,10 +193,12 @@ public void testFindRemoteRefUpdatesTwoRefSpecs() throws IOException { public void testFindRemoteRefUpdatesTrackingRef() throws IOException { remoteConfig.addFetchRefSpec(new RefSpec( "refs/heads/*:refs/remotes/test/*")); - transport = Transport.open(db, remoteConfig); - final Collection result = transport - .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( - "+refs/heads/a:refs/heads/a"))); + + Collection result; + try (Transport transport = Transport.open(db, remoteConfig)) { + result = transport.findRemoteRefUpdatesFor(Collections.nCopies(1, + new RefSpec("+refs/heads/a:refs/heads/a"))); + } assertEquals(1, result.size()); final TrackingRefUpdate tru = result.iterator().next() @@ -225,20 +218,18 @@ public void testLocalTransportWithRelativePath() throws Exception { config.addURI(new URIish("../" + otherDir)); // Should not throw NoRemoteRepositoryException - transport = Transport.open(db, config); + Transport.open(db, config).close(); } @Test public void testLocalTransportFetchWithoutLocalRepository() throws Exception { URIish uri = new URIish("file://" + db.getWorkTree().getAbsolutePath()); - transport = Transport.open(uri); - FetchConnection fetchConnection = transport.openFetch(); - try { - Ref head = fetchConnection.getRef(Constants.HEAD); - assertNotNull(head); - } finally { - fetchConnection.close(); + try (Transport transport = Transport.open(uri)) { + try (FetchConnection fetchConnection = transport.openFetch()) { + Ref head = fetchConnection.getRef(Constants.HEAD); + assertNotNull(head); + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java index 9620089b0..de512761a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java @@ -116,22 +116,17 @@ public FetchResult call() throws GitAPIException, InvalidRemoteException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); - try { - Transport transport = Transport.open(repo, remote); - try { - transport.setCheckFetchedObjects(checkFetchedObjects); - transport.setRemoveDeletedRefs(isRemoveDeletedRefs()); - transport.setDryRun(dryRun); - if (tagOption != null) - transport.setTagOpt(tagOption); - transport.setFetchThin(thin); - configure(transport); + try (Transport transport = Transport.open(repo, remote)) { + transport.setCheckFetchedObjects(checkFetchedObjects); + transport.setRemoveDeletedRefs(isRemoveDeletedRefs()); + transport.setDryRun(dryRun); + if (tagOption != null) + transport.setTagOpt(tagOption); + transport.setFetchThin(thin); + configure(transport); - FetchResult result = transport.fetch(monitor, refSpecs); - return result; - } finally { - transport.close(); - } + FetchResult result = transport.fetch(monitor, refSpecs); + return result; } catch (NoRemoteRepositoryException e) { throw new InvalidRemoteException(MessageFormat.format( JGitText.get().invalidRemote, remote), e); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java index 3363a0fc8..f3527fd80 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java @@ -182,13 +182,9 @@ private Map execute() throws GitAPIException, org.eclipse.jgit.api.errors.TransportException { checkCallable(); - Transport transport = null; - FetchConnection fc = null; - try { - if (repo != null) - transport = Transport.open(repo, remote); - else - transport = Transport.open(new URIish(remote)); + try (Transport transport = repo != null + ? Transport.open(repo, remote) + : Transport.open(new URIish(remote))) { transport.setOptionUploadPack(uploadPack); configure(transport); Collection refSpecs = new ArrayList(1); @@ -199,19 +195,20 @@ private Map execute() throws GitAPIException, refSpecs.add(new RefSpec("refs/heads/*:refs/remotes/origin/*")); //$NON-NLS-1$ Collection refs; Map refmap = new HashMap(); - fc = transport.openFetch(); - refs = fc.getRefs(); - if (refSpecs.isEmpty()) - for (Ref r : refs) - refmap.put(r.getName(), r); - else - for (Ref r : refs) - for (RefSpec rs : refSpecs) - if (rs.matchSource(r)) { - refmap.put(r.getName(), r); - break; - } - return refmap; + try (FetchConnection fc = transport.openFetch()) { + refs = fc.getRefs(); + if (refSpecs.isEmpty()) + for (Ref r : refs) + refmap.put(r.getName(), r); + else + for (Ref r : refs) + for (RefSpec rs : refSpecs) + if (rs.matchSource(r)) { + refmap.put(r.getName(), r); + break; + } + return refmap; + } } catch (URISyntaxException e) { throw new InvalidRemoteException(MessageFormat.format( JGitText.get().invalidRemote, remote)); @@ -223,11 +220,6 @@ private Map execute() throws GitAPIException, throw new org.eclipse.jgit.api.errors.TransportException( e.getMessage(), e); - } finally { - if (fc != null) - fc.close(); - if (transport != null) - transport.close(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java index 0ff9fcea7..da288ec31 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java @@ -59,8 +59,7 @@ * * @see Transport */ -public interface Connection { - +public interface Connection extends AutoCloseable { /** * Get the complete map of refs advertised as available for fetching or * pushing. @@ -108,6 +107,10 @@ public interface Connection { *

* If additional messages were produced by the remote peer, these should * still be retained in the connection instance for {@link #getMessages()}. + *

+ * {@code AutoClosable.close()} declares that it throws {@link Exception}. + * Implementers shouldn't throw checked exceptions. This override narrows + * the signature to prevent them from doing so. */ public void close(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java index 6af153cbc..9e6d1f68f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java @@ -98,7 +98,7 @@ * Transport instances and the connections they create are not thread-safe. * Callers must ensure a transport is accessed by only one thread at a time. */ -public abstract class Transport { +public abstract class Transport implements AutoCloseable { /** Type of operation a Transport is being opened for. */ public enum Operation { /** Transport is to fetch objects locally. */ @@ -1353,6 +1353,10 @@ public abstract PushConnection openPush() throws NotSupportedException, * must close that network socket, disconnecting the two peers. If the * remote repository is actually local (same system) this method must close * any open file handles used to read the "remote" repository. + *

+ * {@code AutoClosable.close()} declares that it throws {@link Exception}. + * Implementers shouldn't throw checked exceptions. This override narrows + * the signature to prevent them from doing so. */ public abstract void close(); } From 2ccea7f05a0f3e783f6a8fa3f07cc5f1001bc950 Mon Sep 17 00:00:00 2001 From: Dave Borowitz Date: Tue, 19 Jan 2016 11:13:40 -0500 Subject: [PATCH 85/89] ChangeIdUtil: Don't throw IOException This could have only happened during the getBytes call. Instead, use Constants.encode, which is a non-throwing implementation. This change is binary compatible with existing code compiled against older versions of JGit, although it might break compilation of previously compiling code due to dead catch blocks. Change-Id: I191fec5cac718657407230de141440e86d0151fb --- .../src/org/eclipse/jgit/util/ChangeIdUtil.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java index 35fc99e54..e14096e59 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java @@ -42,7 +42,6 @@ */ package org.eclipse.jgit.util; -import java.io.IOException; import java.util.regex.Pattern; import org.eclipse.jgit.lib.Constants; @@ -90,12 +89,10 @@ static String clean(String msg) { * The commit message * @return the change id SHA1 string (without the 'I') or null if the * message is not complete enough - * @throws IOException */ public static ObjectId computeChangeId(final ObjectId treeId, final ObjectId firstParentId, final PersonIdent author, - final PersonIdent committer, final String message) - throws IOException { + final PersonIdent committer, final String message) { String cleanMessage = clean(message); if (cleanMessage.length() == 0) return null; @@ -116,8 +113,7 @@ public static ObjectId computeChangeId(final ObjectId treeId, b.append("\n\n"); //$NON-NLS-1$ b.append(cleanMessage); try (ObjectInserter f = new ObjectInserter.Formatter()) { - return f.idFor(Constants.OBJ_COMMIT, // - b.toString().getBytes(Constants.CHARACTER_ENCODING)); + return f.idFor(Constants.OBJ_COMMIT, Constants.encode(b.toString())); } } From 3af05f60805e9b4587c2cb2405d6ef504e3ccee2 Mon Sep 17 00:00:00 2001 From: Matthias Sohn Date: Wed, 20 Jan 2016 01:53:17 +0100 Subject: [PATCH 86/89] Remove declared IOException which is no longer thrown Since 2ccea7f0 ChangeIdUtil.computeChangeId() doesn't throw IOException anymore. Change-Id: I0bf43f2346dadbbfe7e6cbcb38b5525456fbf686 Signed-off-by: Matthias Sohn --- .../src/org/eclipse/jgit/junit/TestRepository.java | 3 +-- .../tst/org/eclipse/jgit/util/ChangeIdUtilTest.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index 3a4f9c788..8439c39c8 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -1155,8 +1155,7 @@ public RevCommit create() throws Exception { return self; } - private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) - throws IOException { + private void insertChangeId(org.eclipse.jgit.lib.CommitBuilder c) { if (changeId == null) return; int idx = ChangeIdUtil.indexOfChangeId(message, "\n"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java index 7273cdbab..aaeb79c64 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/ChangeIdUtilTest.java @@ -45,7 +45,6 @@ import static org.junit.Assert.assertEquals; -import java.io.IOException; import java.util.concurrent.TimeUnit; import org.eclipse.jgit.junit.MockSystemReader; @@ -113,7 +112,7 @@ public void testClean() { } @Test - public void testId() throws IOException { + public void testId() { String msg = "A\nMessage\n"; ObjectId id = ChangeIdUtil.computeChangeId(treeId, parentId, p, q, msg); assertEquals("73f3751208ac92cbb76f9a26ac4a0d9d472e381b", ObjectId From da43d8d79890e561a993a4d90e6a2724a04cd60f Mon Sep 17 00:00:00 2001 From: Christian Halstrick Date: Wed, 14 Oct 2015 16:25:45 +0200 Subject: [PATCH 87/89] Add option to allow empty commits to CommitCommand CommitCommand should allow to specify whether empty commits (commits having the same tree as the sole predecessor commit) are allowed or not. Similar to native git's "--allow-empty" flag. The defaults differ between JGit and native git even after this change. When not specifying paths then by default JGit allows to create empty commits while native git does not. It would be API breaking to change this now. Bug: 460301 Change-Id: I88feb0c3ffb2c686b1d0594e669729b065cda4cb Signed-off-by: Matthias Sohn --- .../eclipse/jgit/api/CommitCommandTest.java | 31 ++++++++++ .../org/eclipse/jgit/api/CommitCommand.java | 42 +++++++++++++ .../jgit/api/errors/EmtpyCommitException.java | 62 +++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java index 0d03047d5..b39a68a22 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java @@ -46,12 +46,15 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.fail; import java.io.File; import java.util.Date; import java.util.List; import java.util.TimeZone; +import org.eclipse.jgit.api.errors.EmtpyCommitException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.dircache.DirCache; @@ -476,6 +479,34 @@ public void commitAmendWithAuthorShouldUseIt() throws Exception { assertEquals("newauthor@example.org", amendedAuthor.getEmailAddress()); } + @Test + public void commitEmptyCommits() throws Exception { + try (Git git = new Git(db)) { + + writeTrashFile("file1", "file1"); + git.add().addFilepattern("file1").call(); + RevCommit initial = git.commit().setMessage("initial commit") + .call(); + + RevCommit emptyFollowUp = git.commit() + .setAuthor("New Author", "newauthor@example.org") + .setMessage("no change").call(); + + assertNotEquals(initial.getId(), emptyFollowUp.getId()); + assertEquals(initial.getTree().getId(), + emptyFollowUp.getTree().getId()); + + try { + git.commit().setAuthor("New Author", "newauthor@example.org") + .setMessage("again no change").setAllowEmpty(false) + .call(); + fail("Didn't get the expected EmtpyCommitException"); + } catch (EmtpyCommitException e) { + // expect this exception + } + } + } + @Test public void commitOnlyShouldCommitUnmergedPathAndNotAffectOthers() throws Exception { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index 4f9a5a317..b5057ad28 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -53,6 +53,7 @@ import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; +import org.eclipse.jgit.api.errors.EmtpyCommitException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoFilepatternException; @@ -130,6 +131,8 @@ public class CommitCommand extends GitCommand { private PrintStream hookOutRedirect; + private Boolean allowEmpty; + /** * @param repo */ @@ -231,6 +234,16 @@ public RevCommit call() throws GitAPIException, NoHeadException, if (insertChangeId) insertChangeId(indexTreeId); + // Check for empty commits + if (headId != null && !allowEmpty.booleanValue()) { + RevCommit headCommit = rw.parseCommit(headId); + headCommit.getTree(); + if (indexTreeId.equals(headCommit.getTree())) { + throw new EmtpyCommitException( + JGitText.get().emptyCommit); + } + } + // Create a Commit object, populate it and write it CommitBuilder commit = new CommitBuilder(); commit.setCommitter(committer); @@ -457,6 +470,8 @@ private DirCache createTemporaryIndex(ObjectId headId, DirCache index, // there must be at least one change if (emptyCommit) + // Would like to throw a EmptyCommitException. But this would break the API + // TODO(ch): Change this in the next release throw new JGitInternalException(JGitText.get().emptyCommit); // update index @@ -510,6 +525,12 @@ private void processOptions(RepositoryState state, RevWalk rw) committer = new PersonIdent(repo); if (author == null && !amend) author = committer; + if (allowEmpty == null) + // JGit allows empty commits by default. Only when pathes are + // specified the commit should not be empty. This behaviour differs + // from native git but can only be adapted in the next release. + // TODO(ch) align the defaults with native git + allowEmpty = (only.isEmpty()) ? Boolean.TRUE : Boolean.FALSE; // when doing a merge commit parse MERGE_HEAD and MERGE_MSG files if (state == RepositoryState.MERGING_RESOLVED @@ -578,6 +599,27 @@ public CommitCommand setMessage(String message) { return this; } + /** + * @param allowEmpty + * whether it should be allowed to create a commit which has the + * same tree as it's sole predecessor (a commit which doesn't + * change anything). By default when creating standard commits + * (without specifying paths) JGit allows to create such commits. + * When this flag is set to false an attempt to create an "empty" + * standard commit will lead to an EmptyCommitException. + *

+ * By default when creating a commit containing only specified + * paths an attempt to create an empty commit leads to a + * {@link JGitInternalException}. By setting this flag to + * true this exception will not be thrown. + * @return {@code this} + * @since 4.2 + */ + public CommitCommand setAllowEmpty(boolean allowEmpty) { + this.allowEmpty = Boolean.valueOf(allowEmpty); + return this; + } + /** * @return the commit message used for the commit */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java new file mode 100644 index 000000000..b3cc1bfcf --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015, Christian Halstrick + * 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.api.errors; + +/** + * Exception thrown when a newly created commit does not contain any changes + * + * @since 4.2 + */ +public class EmtpyCommitException extends GitAPIException { + private static final long serialVersionUID = 1L; + + /** + * @param message + * @param cause + */ + public EmtpyCommitException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param message + */ + public EmtpyCommitException(String message) { + super(message); + } +} From 7182cb2a2632b947a100ea817d5ffbb10ef479b6 Mon Sep 17 00:00:00 2001 From: Christian Halstrick Date: Wed, 28 Oct 2015 10:36:27 +0100 Subject: [PATCH 88/89] Fix ResetCommand to return the resulting ref ResetCommand was not returning the updated ref as a result of the call() method. Since the ResetCommand is always updating the same ref (HEAD) this should always be the HEAD ref. Bug: 440750 Change-Id: I7974975c3ab05e68c208384e69cf0692ded6e8db Signed-off-by: Matthias Sohn --- .../eclipse/jgit/api/ResetCommandTest.java | 56 ++++++++++++------- .../org/eclipse/jgit/api/ResetCommand.java | 4 +- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java index a67f2b912..66f25e8e5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java @@ -65,6 +65,7 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; @@ -139,8 +140,8 @@ public void testHardReset() throws JGitInternalException, AmbiguousObjectException, IOException, GitAPIException { setupRepository(); ObjectId prevHead = db.resolve(Constants.HEAD); - git.reset().setMode(ResetType.HARD).setRef(initialCommit.getName()) - .call(); + assertSameAsHead(git.reset().setMode(ResetType.HARD) + .setRef(initialCommit.getName()).call()); // check if HEAD points to initial commit now ObjectId head = db.resolve(Constants.HEAD); assertEquals(initialCommit, head); @@ -176,8 +177,8 @@ public void testSoftReset() throws JGitInternalException, AmbiguousObjectException, IOException, GitAPIException { setupRepository(); ObjectId prevHead = db.resolve(Constants.HEAD); - git.reset().setMode(ResetType.SOFT).setRef(initialCommit.getName()) - .call(); + assertSameAsHead(git.reset().setMode(ResetType.SOFT) + .setRef(initialCommit.getName()).call()); // check if HEAD points to initial commit now ObjectId head = db.resolve(Constants.HEAD); assertEquals(initialCommit, head); @@ -197,8 +198,8 @@ public void testMixedReset() throws JGitInternalException, AmbiguousObjectException, IOException, GitAPIException { setupRepository(); ObjectId prevHead = db.resolve(Constants.HEAD); - git.reset().setMode(ResetType.MIXED).setRef(initialCommit.getName()) - .call(); + assertSameAsHead(git.reset().setMode(ResetType.MIXED) + .setRef(initialCommit.getName()).call()); // check if HEAD points to initial commit now ObjectId head = db.resolve(Constants.HEAD); assertEquals(initialCommit, head); @@ -241,7 +242,8 @@ public void testMixedResetRetainsSizeAndModifiedTime() throws Exception { assertTrue(bEntry.getLength() > 0); assertTrue(bEntry.getLastModified() > 0); - git.reset().setMode(ResetType.MIXED).setRef(commit2.getName()).call(); + assertSameAsHead(git.reset().setMode(ResetType.MIXED) + .setRef(commit2.getName()).call()); cache = db.readDirCache(); @@ -280,7 +282,7 @@ public void testMixedResetWithUnmerged() throws Exception { + "[a.txt, mode:100644, stage:3]", indexState(0)); - git.reset().setMode(ResetType.MIXED).call(); + assertSameAsHead(git.reset().setMode(ResetType.MIXED).call()); assertEquals("[a.txt, mode:100644]" + "[b.txt, mode:100644]", indexState(0)); @@ -298,8 +300,8 @@ public void testPathsReset() throws Exception { // 'a.txt' has already been modified in setupRepository // 'notAddedToIndex.txt' has been added to repository - git.reset().addPath(indexFile.getName()) - .addPath(untrackedFile.getName()).call(); + assertSameAsHead(git.reset().addPath(indexFile.getName()) + .addPath(untrackedFile.getName()).call()); DirCacheEntry postReset = DirCache.read(db.getIndexFile(), db.getFS()) .getEntry(indexFile.getName()); @@ -329,7 +331,7 @@ public void testPathsResetOnDirs() throws Exception { git.add().addFilepattern(untrackedFile.getName()).call(); // 'dir/b.txt' has already been modified in setupRepository - git.reset().addPath("dir").call(); + assertSameAsHead(git.reset().addPath("dir").call()); DirCacheEntry postReset = DirCache.read(db.getIndexFile(), db.getFS()) .getEntry("dir/b.txt"); @@ -358,9 +360,9 @@ public void testPathsResetWithRef() throws Exception { // 'a.txt' has already been modified in setupRepository // 'notAddedToIndex.txt' has been added to repository // reset to the inital commit - git.reset().setRef(initialCommit.getName()) - .addPath(indexFile.getName()) - .addPath(untrackedFile.getName()).call(); + assertSameAsHead(git.reset().setRef(initialCommit.getName()) + .addPath(indexFile.getName()).addPath(untrackedFile.getName()) + .call()); // check that HEAD hasn't moved ObjectId head = db.resolve(Constants.HEAD); @@ -397,7 +399,7 @@ public void testPathsResetWithUnmerged() throws Exception { + "[b.txt, mode:100644]", indexState(0)); - git.reset().addPath(file).call(); + assertSameAsHead(git.reset().addPath(file).call()); assertEquals("[a.txt, mode:100644]" + "[b.txt, mode:100644]", indexState(0)); @@ -409,7 +411,7 @@ public void testPathsResetOnUnbornBranch() throws Exception { writeTrashFile("a.txt", "content"); git.add().addFilepattern("a.txt").call(); // Should assume an empty tree, like in C Git 1.8.2 - git.reset().addPath("a.txt").call(); + assertSameAsHead(git.reset().addPath("a.txt").call()); DirCache cache = db.readDirCache(); DirCacheEntry aEntry = cache.getEntry("a.txt"); @@ -421,7 +423,8 @@ public void testPathsResetToNonexistingRef() throws Exception { git = new Git(db); writeTrashFile("a.txt", "content"); git.add().addFilepattern("a.txt").call(); - git.reset().setRef("doesnotexist").addPath("a.txt").call(); + assertSameAsHead( + git.reset().setRef("doesnotexist").addPath("a.txt").call()); } @Test @@ -431,7 +434,7 @@ public void testResetDefaultMode() throws Exception { git.add().addFilepattern("a.txt").call(); writeTrashFile("a.txt", "modified"); // should use default mode MIXED - git.reset().call(); + assertSameAsHead(git.reset().call()); DirCache cache = db.readDirCache(); DirCacheEntry aEntry = cache.getEntry("a.txt"); @@ -452,7 +455,7 @@ public void testHardResetOnTag() throws Exception { git.add().addFilepattern(untrackedFile.getName()).call(); - git.reset().setRef(tagName).setMode(HARD).call(); + assertSameAsHead(git.reset().setRef(tagName).setMode(HARD).call()); ObjectId head = db.resolve(Constants.HEAD); assertEquals(secondCommit, head); @@ -486,7 +489,8 @@ public void testHardResetAfterSquashMerge() throws Exception { result.getMergeStatus()); assertNotNull(db.readSquashCommitMsg()); - g.reset().setMode(ResetType.HARD).setRef(first.getName()).call(); + assertSameAsHead(g.reset().setMode(ResetType.HARD) + .setRef(first.getName()).call()); assertNull(db.readSquashCommitMsg()); } @@ -497,7 +501,7 @@ public void testHardResetOnUnbornBranch() throws Exception { File fileA = writeTrashFile("a.txt", "content"); git.add().addFilepattern("a.txt").call(); // Should assume an empty tree, like in C Git 1.8.2 - git.reset().setMode(ResetType.HARD).call(); + assertSameAsHead(git.reset().setMode(ResetType.HARD).call()); DirCache cache = db.readDirCache(); DirCacheEntry aEntry = cache.getEntry("a.txt"); @@ -558,4 +562,14 @@ private boolean inIndex(String path) throws IOException { return dc.getEntry(path) != null; } + /** + * Asserts that a certain ref is similar to repos HEAD. + * @param ref + * @throws IOException + */ + private void assertSameAsHead(Ref ref) throws IOException { + Ref headRef = db.getRef(Constants.HEAD); + assertEquals(headRef.getName(), ref.getName()); + assertEquals(headRef.getObjectId(), ref.getObjectId()); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java index 8f4bc4f26..4c91e6c17 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java @@ -190,10 +190,8 @@ public Ref call() throws GitAPIException, CheckoutConflictException { ObjectId origHead = ru.getOldObjectId(); if (origHead != null) repo.writeOrigHead(origHead); - result = ru.getRef(); - } else { - result = repo.getRef(Constants.HEAD); } + result = repo.exactRef(Constants.HEAD); if (mode == null) mode = ResetType.MIXED; From 2262a794b48ea866d1de2a6a44b79accb8224a27 Mon Sep 17 00:00:00 2001 From: Shawn Pearce Date: Wed, 20 Jan 2016 07:41:08 -0800 Subject: [PATCH 89/89] TreeWalk: Remove CorruptObjectException from addTree(AbstractTreeIterator) This form of addTree() does not parse any objects and cannot throw the declared checked exception. Callers are being forced to try-catch CorruptObjectException that cannot occur when the iterator instance has already been constructed. Change-Id: Id338035302903bab81569d1576eab063eee0885a --- .../src/org/eclipse/jgit/treewalk/TreeWalk.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java index 83fada4f9..5cd713da7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java @@ -574,18 +574,13 @@ public int addTree(final AnyObjectId id) throws MissingObjectException, * @param p * an iterator to walk over. The iterator should be new, with no * parent, and should still be positioned before the first entry. - * The tree which the iterator operates on must have the same root - * as other trees in the walk. - * + * The tree which the iterator operates on must have the same + * root as other trees in the walk. * @return position of this tree within the walker. - * @throws CorruptObjectException - * the iterator was unable to obtain its first entry, due to - * possible data corruption within the backing data store. */ - public int addTree(final AbstractTreeIterator p) - throws CorruptObjectException { - final int n = trees.length; - final AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1]; + public int addTree(AbstractTreeIterator p) { + int n = trees.length; + AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1]; System.arraycopy(trees, 0, newTrees, 0, n); newTrees[n] = p;