Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn O. Pearce2010-01-23 19:11:06 +0000
committerShawn O. Pearce2010-01-23 19:11:12 +0000
commit3103abe4e2fe6792a1eb4908726002f6105d09f0 (patch)
tree1336aeaae86392c792d64d86f8bbf9824620b827
parent407fe631aec08ec4b0f24462b3fa2cee67a83914 (diff)
parent36f05a9c27e6961b10df0b65014ffc869f4f8686 (diff)
downloadjgit-3103abe4e2fe6792a1eb4908726002f6105d09f0.tar.gz
jgit-3103abe4e2fe6792a1eb4908726002f6105d09f0.tar.xz
jgit-3103abe4e2fe6792a1eb4908726002f6105d09f0.zip
Merge branch 'ref-abstract'
* ref-abstract: Optimize RefAdvertiser performance by avoiding sorting branch: Add -m option to rename a branch Replace writeSymref with RefUpdate.link Rewrite reference handling to be abstract and accurate Create new RefList and RefMap utility types Change-Id: If43aacf5aa4013edbd0a6e84d84c4f9e94de5be0
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java5
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java43
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java9
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java3
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java16
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java6
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java115
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java1024
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java59
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java103
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java4
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java129
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java22
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java31
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java27
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java432
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java472
-rw-r--r--org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java36
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java188
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java168
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java32
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java576
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java1006
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java225
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java156
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java158
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java205
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java331
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java33
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java177
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java121
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java29
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java29
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java438
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java423
44 files changed, 5633 insertions, 1286 deletions
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
index 7bad517170..b766196fdc 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
@@ -47,7 +47,6 @@ import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
import static org.eclipse.jgit.http.server.ServletUtils.send;
import java.io.IOException;
-import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServlet;
@@ -98,9 +97,9 @@ class InfoRefsServlet extends HttpServlet {
adv.init(walk, ADVERTISED);
adv.setDerefTags(true);
- Map<String, Ref> refs = new HashMap<String, Ref>(db.getAllRefs());
+ Map<String, Ref> refs = db.getAllRefs();
refs.remove(Constants.HEAD);
- adv.send(refs.values());
+ adv.send(refs);
return out.toString().getBytes(Constants.CHARACTER_ENCODING);
}
}
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 60dbe27acb..7a1dd16043 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
@@ -50,18 +50,19 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import org.kohsuke.args4j.Argument;
-import org.kohsuke.args4j.ExampleMode;
-import org.kohsuke.args4j.Option;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefComparator;
+import org.eclipse.jgit.lib.RefRename;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.pgm.opt.CmdLineParser;
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 = "List, create, or delete branches")
class Branch extends TextBuiltin {
@@ -81,6 +82,9 @@ class Branch extends TextBuiltin {
@Option(name = "--create-force", aliases = { "-f" }, usage = "force create branch even exists")
private boolean createForce = false;
+ @Option(name = "-m", usage = "move/rename a branch")
+ private boolean rename = false;
+
@Option(name = "--verbose", aliases = { "-v" }, usage = "be verbose")
private boolean verbose = false;
@@ -102,7 +106,36 @@ class Branch extends TextBuiltin {
if (branches.size() > 2)
throw die("Too many refs given\n" + new CmdLineParser(this).printExample(ExampleMode.ALL));
- if (branches.size() > 0) {
+ if (rename) {
+ String src, dst;
+ if (branches.size() == 1) {
+ final Ref head = db.getRef(Constants.HEAD);
+ if (head != null && head.isSymbolic())
+ src = head.getLeaf().getName();
+ else
+ throw die("Cannot rename detached HEAD");
+ dst = branches.get(0);
+ } else {
+ src = branches.get(0);
+ final Ref old = db.getRef(src);
+ if (old == null)
+ throw die(String.format("%s does not exist", src));
+ if (!old.getName().startsWith(Constants.R_HEADS))
+ throw die(String.format("%s is not a branch", src));
+ src = old.getName();
+ dst = branches.get(1);
+ }
+
+ if (!dst.startsWith(Constants.R_HEADS))
+ dst = Constants.R_HEADS + dst;
+ if (!Repository.isValidRefName(dst))
+ throw die(String.format("%s is not a valid ref name", dst));
+
+ RefRename r = db.renameRef(src, dst);
+ if (r.rename() != Result.RENAMED)
+ throw die(String.format("%s cannot be renamed", src));
+
+ } else if (branches.size() > 0) {
String newHead = branches.get(0);
String startBranch;
if (branches.size() == 2)
@@ -143,7 +176,7 @@ class Branch extends TextBuiltin {
Ref head = refs.get(Constants.HEAD);
// This can happen if HEAD is stillborn
if (head != null) {
- String current = head.getName();
+ String current = head.getLeaf().getName();
if (current.equals(Constants.HEAD))
addRef("(no branch)", head);
addRefs(refs, Constants.R_HEADS, !remote);
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 10bb357e26..a6c50ff19a 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
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008-2010, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -164,8 +164,11 @@ class Clone extends AbstractFetchCommand {
private void doCheckout(final Ref branch) throws IOException {
if (branch == null)
throw die("cannot checkout; no HEAD advertised by remote");
- if (!Constants.HEAD.equals(branch.getName()))
- db.writeSymref(Constants.HEAD, branch.getName());
+ if (!Constants.HEAD.equals(branch.getName())) {
+ RefUpdate u = db.updateRef(Constants.HEAD);
+ u.disableRefLog();
+ u.link(branch.getName());
+ }
final Commit commit = db.mapCommit(branch.getObjectId());
final RefUpdate u = db.updateRef(Constants.HEAD);
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
index ecaf19bd12..4b5975669f 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2010, Google Inc.
* Copyright (C) 2006-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log.
@@ -92,7 +93,7 @@ class Log extends RevWalkTextBuiltin {
if (list != null) {
out.print(" (");
for (Iterator<Ref> i = list.iterator(); i.hasNext(); ) {
- out.print(i.next().getOrigName());
+ out.print(i.next().getName());
if (i.hasNext())
out.print(" ");
}
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java
index 7dbb21c5d5..d34f373db2 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2010, Google Inc.
* Copyright (C) 2008, Jonas Fonseca <fonseca@diku.dk>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log.
@@ -44,21 +45,32 @@
package org.eclipse.jgit.pgm;
-import java.util.TreeMap;
+import java.util.Map;
+import java.util.SortedMap;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefComparator;
+import org.eclipse.jgit.util.RefMap;
class ShowRef extends TextBuiltin {
@Override
protected void run() throws Exception {
- for (final Ref r : new TreeMap<String, Ref>(db.getAllRefs()).values()) {
+ for (final Ref r : getSortedRefs()) {
show(r.getObjectId(), r.getName());
if (r.getPeeledObjectId() != null)
show(r.getPeeledObjectId(), r.getName() + "^{}");
}
}
+ private Iterable<Ref> getSortedRefs() {
+ Map<String, Ref> all = db.getAllRefs();
+ if (all instanceof RefMap
+ || (all instanceof SortedMap && ((SortedMap) all).comparator() == null))
+ return all.values();
+ return RefComparator.sort(all.values());
+ }
+
private void show(final AnyObjectId id, final String name) {
out.print(id.name());
out.print('\t');
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
index 50b889849e..0a5f2a0c9a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2009-2010, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -63,6 +63,7 @@ import org.eclipse.jgit.lib.Commit;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.LockFile;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ObjectWriter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
@@ -303,7 +304,8 @@ class RebuildCommitGraph extends TextBuiltin {
}
throw new MissingObjectException(id, type);
}
- refs.put(name, new Ref(Ref.Storage.PACKED, name, id));
+ refs.put(name, new ObjectIdRef.Unpeeled(Ref.Storage.PACKED,
+ name, id));
}
} finally {
br.close();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java
new file mode 100644
index 0000000000..1774a428d4
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010, 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;
+
+import junit.framework.TestCase;
+
+public class ObjectIdRefTest extends TestCase {
+ private static final ObjectId ID_A = ObjectId
+ .fromString("41eb0d88f833b558bddeb269b7ab77399cdf98ed");
+
+ private static final ObjectId ID_B = ObjectId
+ .fromString("698dd0b8d0c299f080559a1cffc7fe029479a408");
+
+ private static final String name = "refs/heads/a.test.ref";
+
+ public void testConstructor_PeeledStatusNotKnown() {
+ ObjectIdRef r;
+
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID_A);
+ assertSame(Ref.Storage.LOOSE, r.getStorage());
+ assertSame(name, r.getName());
+ assertSame(ID_A, r.getObjectId());
+ assertFalse("not peeled", r.isPeeled());
+ assertNull("no peel id", r.getPeeledObjectId());
+ assertSame("leaf is this", r, r.getLeaf());
+ assertSame("target is this", r, r.getTarget());
+ assertFalse("not symbolic", r.isSymbolic());
+
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.PACKED, name, ID_A);
+ assertSame(Ref.Storage.PACKED, r.getStorage());
+
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE_PACKED, name, ID_A);
+ assertSame(Ref.Storage.LOOSE_PACKED, r.getStorage());
+
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, name, null);
+ assertSame(Ref.Storage.NEW, r.getStorage());
+ assertSame(name, r.getName());
+ assertNull("no id on new ref", r.getObjectId());
+ assertFalse("not peeled", r.isPeeled());
+ assertNull("no peel id", r.getPeeledObjectId());
+ assertSame("leaf is this", r, r.getLeaf());
+ assertSame("target is this", r, r.getTarget());
+ assertFalse("not symbolic", r.isSymbolic());
+ }
+
+ public void testConstructor_Peeled() {
+ ObjectIdRef r;
+
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID_A);
+ assertSame(Ref.Storage.LOOSE, r.getStorage());
+ assertSame(name, r.getName());
+ assertSame(ID_A, r.getObjectId());
+ assertFalse("not peeled", r.isPeeled());
+ assertNull("no peel id", r.getPeeledObjectId());
+ assertSame("leaf is this", r, r.getLeaf());
+ assertSame("target is this", r, r.getTarget());
+ assertFalse("not symbolic", r.isSymbolic());
+
+ r = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, name, ID_A);
+ assertTrue("is peeled", r.isPeeled());
+ assertNull("no peel id", r.getPeeledObjectId());
+
+ r = new ObjectIdRef.PeeledTag(Ref.Storage.LOOSE, name, ID_A, ID_B);
+ assertTrue("is peeled", r.isPeeled());
+ assertSame(ID_B, r.getPeeledObjectId());
+ }
+
+ public void testToString() {
+ ObjectIdRef r;
+
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID_A);
+ assertEquals("Ref[" + name + "=" + ID_A.name() + "]", r.toString());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java
new file mode 100644
index 0000000000..a2812901bc
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java
@@ -0,0 +1,1024 @@
+/*
+ * Copyright (C) 2010, 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;
+
+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 java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTag;
+
+public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
+ private Repository diskRepo;
+
+ private TestRepository repo;
+
+ private RefDirectory refdir;
+
+ private RevCommit A;
+
+ private RevCommit B;
+
+ private RevTag v1_0;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ diskRepo = createBareRepository();
+ refdir = (RefDirectory) diskRepo.getRefDatabase();
+
+ repo = new TestRepository(diskRepo);
+ A = repo.commit().create();
+ B = repo.commit(repo.getRevWalk().parseCommit(A));
+ v1_0 = repo.tag("v1_0", B);
+ repo.getRevWalk().parseBody(v1_0);
+ }
+
+ public void testCreate() throws IOException {
+ // setUp above created the directory. We just have to test it.
+ File d = diskRepo.getDirectory();
+ assertSame(diskRepo, refdir.getRepository());
+
+ assertTrue(new File(d, "refs").isDirectory());
+ assertTrue(new File(d, "logs").isDirectory());
+ assertTrue(new File(d, "logs/refs").isDirectory());
+ assertFalse(new File(d, "packed-refs").exists());
+
+ assertTrue(new File(d, "refs/heads").isDirectory());
+ assertTrue(new File(d, "refs/tags").isDirectory());
+ assertEquals(2, new File(d, "refs").list().length);
+ assertEquals(0, new File(d, "refs/heads").list().length);
+ assertEquals(0, new File(d, "refs/tags").list().length);
+
+ assertTrue(new File(d, "logs/refs/heads").isDirectory());
+ assertFalse(new File(d, "logs/HEAD").exists());
+ assertEquals(0, new File(d, "logs/refs/heads").list().length);
+
+ assertEquals("ref: refs/heads/master\n", read(new File(d, HEAD)));
+ }
+
+ public void testGetRefs_EmptyDatabase() throws IOException {
+ Map<String, Ref> all;
+
+ all = refdir.getRefs(RefDatabase.ALL);
+ assertTrue("no references", all.isEmpty());
+
+ all = refdir.getRefs(R_HEADS);
+ assertTrue("no references", all.isEmpty());
+
+ all = refdir.getRefs(R_TAGS);
+ assertTrue("no references", all.isEmpty());
+ }
+
+ public void testGetRefs_HeadOnOneBranch() throws IOException {
+ Map<String, Ref> all;
+ Ref head, master;
+
+ writeLooseRef("refs/heads/master", A);
+
+ all = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(2, all.size());
+ assertTrue("has HEAD", all.containsKey(HEAD));
+ assertTrue("has master", all.containsKey("refs/heads/master"));
+
+ head = all.get(HEAD);
+ 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(LOOSE, master.getStorage());
+ assertEquals(A, master.getObjectId());
+ }
+
+ public void testGetRefs_DeatchedHead1() throws IOException {
+ Map<String, Ref> all;
+ Ref head;
+
+ writeLooseRef(HEAD, A);
+ BUG_WorkAroundRacyGitIssues(HEAD);
+
+ all = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(1, all.size());
+ assertTrue("has HEAD", all.containsKey(HEAD));
+
+ head = all.get(HEAD);
+
+ assertEquals(HEAD, head.getName());
+ assertFalse(head.isSymbolic());
+ assertSame(LOOSE, head.getStorage());
+ assertEquals(A, head.getObjectId());
+ }
+
+ public void testGetRefs_DeatchedHead2() throws IOException {
+ Map<String, Ref> all;
+ Ref head, master;
+
+ writeLooseRef(HEAD, A);
+ writeLooseRef("refs/heads/master", B);
+ BUG_WorkAroundRacyGitIssues(HEAD);
+
+ all = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(2, all.size());
+
+ head = all.get(HEAD);
+ master = all.get("refs/heads/master");
+
+ assertEquals(HEAD, head.getName());
+ assertFalse(head.isSymbolic());
+ assertSame(LOOSE, head.getStorage());
+ assertEquals(A, head.getObjectId());
+
+ assertEquals("refs/heads/master", master.getName());
+ assertFalse(master.isSymbolic());
+ assertSame(LOOSE, master.getStorage());
+ assertEquals(B, master.getObjectId());
+ }
+
+ public void testGetRefs_DeeplyNestedBranch() throws IOException {
+ String name = "refs/heads/a/b/c/d/e/f/g/h/i/j/k";
+ Map<String, Ref> all;
+ Ref r;
+
+ writeLooseRef(name, A);
+
+ all = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(1, all.size());
+
+ r = all.get(name);
+ assertEquals(name, r.getName());
+ assertFalse(r.isSymbolic());
+ assertSame(LOOSE, r.getStorage());
+ assertEquals(A, r.getObjectId());
+ }
+
+ public void testGetRefs_HeadBranchNotBorn() throws IOException {
+ Map<String, Ref> all;
+ Ref a, b;
+
+ writeLooseRef("refs/heads/A", A);
+ writeLooseRef("refs/heads/B", B);
+
+ all = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(2, all.size());
+ assertFalse("no HEAD", all.containsKey(HEAD));
+
+ a = all.get("refs/heads/A");
+ 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());
+ }
+
+ public void testGetRefs_LooseOverridesPacked() throws IOException {
+ Map<String, Ref> heads;
+ Ref a;
+
+ writeLooseRef("refs/heads/master", B);
+ writePackedRef("refs/heads/master", A);
+
+ heads = refdir.getRefs(R_HEADS);
+ assertEquals(1, heads.size());
+
+ a = heads.get("master");
+ assertEquals("refs/heads/master", a.getName());
+ assertEquals(B, a.getObjectId());
+ }
+
+ public void testGetRefs_IgnoresGarbageRef1() throws IOException {
+ Map<String, Ref> heads;
+ Ref a;
+
+ writeLooseRef("refs/heads/A", A);
+ write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "FAIL\n");
+
+ heads = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(1, heads.size());
+
+ a = heads.get("refs/heads/A");
+ assertEquals("refs/heads/A", a.getName());
+ assertEquals(A, a.getObjectId());
+ }
+
+ public void testGetRefs_IgnoresGarbageRef2() throws IOException {
+ Map<String, Ref> heads;
+ Ref a;
+
+ writeLooseRef("refs/heads/A", A);
+ write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "");
+
+ heads = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(1, heads.size());
+
+ a = heads.get("refs/heads/A");
+ assertEquals("refs/heads/A", a.getName());
+ assertEquals(A, a.getObjectId());
+ }
+
+ public void testGetRefs_IgnoresGarbageRef3() throws IOException {
+ Map<String, Ref> heads;
+ Ref a;
+
+ writeLooseRef("refs/heads/A", A);
+ write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "\n");
+
+ heads = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(1, heads.size());
+
+ a = heads.get("refs/heads/A");
+ assertEquals("refs/heads/A", a.getName());
+ assertEquals(A, a.getObjectId());
+ }
+
+ public void testGetRefs_IgnoresGarbageRef4() throws IOException {
+ Map<String, Ref> heads;
+ Ref a, b, c;
+
+ writeLooseRef("refs/heads/A", A);
+ writeLooseRef("refs/heads/B", B);
+ writeLooseRef("refs/heads/C", A);
+ heads = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(3, heads.size());
+ assertTrue(heads.containsKey("refs/heads/A"));
+ assertTrue(heads.containsKey("refs/heads/B"));
+ assertTrue(heads.containsKey("refs/heads/C"));
+
+ writeLooseRef("refs/heads/B", "FAIL\n");
+ BUG_WorkAroundRacyGitIssues("refs/heads/B");
+
+ heads = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(2, heads.size());
+
+ a = heads.get("refs/heads/A");
+ b = heads.get("refs/heads/B");
+ c = heads.get("refs/heads/C");
+
+ assertEquals("refs/heads/A", a.getName());
+ assertEquals(A, a.getObjectId());
+
+ assertNull("no refs/heads/B", b);
+
+ assertEquals("refs/heads/C", c.getName());
+ assertEquals(A, c.getObjectId());
+ }
+
+ public void testGetRefs_InvalidName() throws IOException {
+ writeLooseRef("refs/heads/A", A);
+
+ assertTrue("empty refs/heads", refdir.getRefs("refs/heads").isEmpty());
+ assertTrue("empty objects", refdir.getRefs("objects").isEmpty());
+ assertTrue("empty objects/", refdir.getRefs("objects/").isEmpty());
+ }
+
+ public void testGetRefs_HeadsOnly_AllLoose() throws IOException {
+ Map<String, Ref> heads;
+ Ref a, b;
+
+ writeLooseRef("refs/heads/A", A);
+ writeLooseRef("refs/heads/B", B);
+ writeLooseRef("refs/tags/v1.0", v1_0);
+
+ heads = refdir.getRefs(R_HEADS);
+ assertEquals(2, heads.size());
+
+ a = heads.get("A");
+ b = heads.get("B");
+
+ assertEquals("refs/heads/A", a.getName());
+ assertEquals("refs/heads/B", b.getName());
+
+ assertEquals(A, a.getObjectId());
+ assertEquals(B, b.getObjectId());
+ }
+
+ public void testGetRefs_HeadsOnly_AllPacked1() throws IOException {
+ Map<String, Ref> heads;
+ Ref a;
+
+ deleteLooseRef(HEAD);
+ writePackedRef("refs/heads/A", A);
+
+ heads = refdir.getRefs(R_HEADS);
+ assertEquals(1, heads.size());
+
+ a = heads.get("A");
+
+ assertEquals("refs/heads/A", a.getName());
+ assertEquals(A, a.getObjectId());
+ }
+
+ public void testGetRefs_HeadsOnly_SymrefToPacked() throws IOException {
+ Map<String, Ref> heads;
+ Ref master, other;
+
+ writeLooseRef("refs/heads/other", "ref: refs/heads/master\n");
+ writePackedRef("refs/heads/master", A);
+
+ heads = refdir.getRefs(R_HEADS);
+ assertEquals(2, heads.size());
+
+ master = heads.get("master");
+ 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());
+ }
+
+ public void testGetRefs_HeadsOnly_Mixed() throws IOException {
+ Map<String, Ref> heads;
+ Ref a, b;
+
+ writeLooseRef("refs/heads/A", A);
+ writeLooseRef("refs/heads/B", B);
+ writePackedRef("refs/tags/v1.0", v1_0);
+
+ heads = refdir.getRefs(R_HEADS);
+ assertEquals(2, heads.size());
+
+ a = heads.get("A");
+ b = heads.get("B");
+
+ assertEquals("refs/heads/A", a.getName());
+ assertEquals("refs/heads/B", b.getName());
+
+ assertEquals(A, a.getObjectId());
+ assertEquals(B, b.getObjectId());
+ }
+
+ public void testGetRefs_TagsOnly_AllLoose() throws IOException {
+ Map<String, Ref> tags;
+ Ref a;
+
+ writeLooseRef("refs/heads/A", A);
+ writeLooseRef("refs/tags/v1.0", v1_0);
+
+ tags = refdir.getRefs(R_TAGS);
+ assertEquals(1, tags.size());
+
+ a = tags.get("v1.0");
+
+ assertEquals("refs/tags/v1.0", a.getName());
+ assertEquals(v1_0, a.getObjectId());
+ }
+
+ public void testGetRefs_TagsOnly_AllPacked() throws IOException {
+ Map<String, Ref> tags;
+ Ref a;
+
+ deleteLooseRef(HEAD);
+ writePackedRef("refs/tags/v1.0", v1_0);
+
+ tags = refdir.getRefs(R_TAGS);
+ assertEquals(1, tags.size());
+
+ a = tags.get("v1.0");
+
+ assertEquals("refs/tags/v1.0", a.getName());
+ assertEquals(v1_0, a.getObjectId());
+ }
+
+ public void testGetRefs_DiscoversNewLoose1() throws IOException {
+ Map<String, Ref> orig, next;
+ Ref orig_r, next_r;
+
+ writeLooseRef("refs/heads/master", A);
+ orig = refdir.getRefs(RefDatabase.ALL);
+
+ writeLooseRef("refs/heads/next", B);
+ next = refdir.getRefs(RefDatabase.ALL);
+
+ assertEquals(2, orig.size());
+ assertEquals(3, next.size());
+
+ assertFalse(orig.containsKey("refs/heads/next"));
+ assertTrue(next.containsKey("refs/heads/next"));
+
+ orig_r = orig.get("refs/heads/master");
+ next_r = next.get("refs/heads/master");
+ assertEquals(A, orig_r.getObjectId());
+ assertSame("uses cached instance", orig_r, next_r);
+ assertSame("same HEAD", orig_r, orig.get(HEAD).getTarget());
+ assertSame("same HEAD", orig_r, next.get(HEAD).getTarget());
+
+ next_r = next.get("refs/heads/next");
+ assertSame(LOOSE, next_r.getStorage());
+ assertEquals(B, next_r.getObjectId());
+ }
+
+ public void testGetRefs_DiscoversNewLoose2() throws IOException {
+ Map<String, Ref> orig, next, news;
+
+ writeLooseRef("refs/heads/pu", A);
+ orig = refdir.getRefs(RefDatabase.ALL);
+
+ writeLooseRef("refs/heads/new/B", B);
+ news = refdir.getRefs("refs/heads/new/");
+ next = refdir.getRefs(RefDatabase.ALL);
+
+ assertEquals(1, orig.size());
+ assertEquals(2, next.size());
+ assertEquals(1, news.size());
+
+ assertTrue(orig.containsKey("refs/heads/pu"));
+ assertTrue(next.containsKey("refs/heads/pu"));
+ assertFalse(news.containsKey("refs/heads/pu"));
+
+ assertFalse(orig.containsKey("refs/heads/new/B"));
+ assertTrue(next.containsKey("refs/heads/new/B"));
+ assertTrue(news.containsKey("B"));
+ }
+
+ public void testGetRefs_DiscoversModifiedLoose() throws IOException {
+ Map<String, Ref> all;
+
+ writeLooseRef("refs/heads/master", A);
+ all = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(A, all.get(HEAD).getObjectId());
+
+ writeLooseRef("refs/heads/master", B);
+ BUG_WorkAroundRacyGitIssues("refs/heads/master");
+ all = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(B, all.get(HEAD).getObjectId());
+ }
+
+ public void testGetRef_DiscoversModifiedLoose() throws IOException {
+ Map<String, Ref> all;
+
+ writeLooseRef("refs/heads/master", A);
+ all = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(A, all.get(HEAD).getObjectId());
+
+ writeLooseRef("refs/heads/master", B);
+ BUG_WorkAroundRacyGitIssues("refs/heads/master");
+
+ Ref master = refdir.getRef("refs/heads/master");
+ assertEquals(B, master.getObjectId());
+ }
+
+ public void testGetRefs_DiscoversDeletedLoose1() throws IOException {
+ Map<String, Ref> orig, next;
+ Ref orig_r, next_r;
+
+ writeLooseRef("refs/heads/B", B);
+ writeLooseRef("refs/heads/master", A);
+ orig = refdir.getRefs(RefDatabase.ALL);
+
+ deleteLooseRef("refs/heads/B");
+ next = refdir.getRefs(RefDatabase.ALL);
+
+ assertEquals(3, orig.size());
+ assertEquals(2, next.size());
+
+ assertTrue(orig.containsKey("refs/heads/B"));
+ assertFalse(next.containsKey("refs/heads/B"));
+
+ orig_r = orig.get("refs/heads/master");
+ next_r = next.get("refs/heads/master");
+ assertEquals(A, orig_r.getObjectId());
+ assertSame("uses cached instance", orig_r, next_r);
+ assertSame("same HEAD", orig_r, orig.get(HEAD).getTarget());
+ assertSame("same HEAD", orig_r, next.get(HEAD).getTarget());
+
+ orig_r = orig.get("refs/heads/B");
+ assertSame(LOOSE, orig_r.getStorage());
+ assertEquals(B, orig_r.getObjectId());
+ }
+
+ public void testGetRef_DiscoversDeletedLoose() throws IOException {
+ Map<String, Ref> all;
+
+ writeLooseRef("refs/heads/master", A);
+ all = refdir.getRefs(RefDatabase.ALL);
+ assertEquals(A, all.get(HEAD).getObjectId());
+
+ deleteLooseRef("refs/heads/master");
+ assertNull(refdir.getRef("refs/heads/master"));
+ assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty());
+ }
+
+ public void testGetRefs_DiscoversDeletedLoose2() throws IOException {
+ Map<String, Ref> orig, next;
+
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/pu", B);
+ orig = refdir.getRefs(RefDatabase.ALL);
+
+ deleteLooseRef("refs/heads/pu");
+ next = refdir.getRefs(RefDatabase.ALL);
+
+ assertEquals(3, orig.size());
+ assertEquals(2, next.size());
+
+ assertTrue(orig.containsKey("refs/heads/pu"));
+ assertFalse(next.containsKey("refs/heads/pu"));
+ }
+
+ public void testGetRefs_DiscoversDeletedLoose3() throws IOException {
+ Map<String, Ref> orig, next;
+
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/next", B);
+ writeLooseRef("refs/heads/pu", B);
+ writeLooseRef("refs/tags/v1.0", v1_0);
+ orig = refdir.getRefs(RefDatabase.ALL);
+
+ deleteLooseRef("refs/heads/pu");
+ deleteLooseRef("refs/heads/next");
+ next = refdir.getRefs(RefDatabase.ALL);
+
+ assertEquals(5, orig.size());
+ assertEquals(3, next.size());
+
+ assertTrue(orig.containsKey("refs/heads/pu"));
+ assertTrue(orig.containsKey("refs/heads/next"));
+ assertFalse(next.containsKey("refs/heads/pu"));
+ assertFalse(next.containsKey("refs/heads/next"));
+ }
+
+ public void testGetRefs_DiscoversDeletedLoose4() throws IOException {
+ Map<String, Ref> orig, next;
+ Ref orig_r, next_r;
+
+ writeLooseRef("refs/heads/B", B);
+ writeLooseRef("refs/heads/master", A);
+ orig = refdir.getRefs(RefDatabase.ALL);
+
+ deleteLooseRef("refs/heads/master");
+ next = refdir.getRefs("refs/heads/");
+
+ assertEquals(3, orig.size());
+ assertEquals(1, next.size());
+
+ assertTrue(orig.containsKey("refs/heads/B"));
+ assertTrue(orig.containsKey("refs/heads/master"));
+ assertTrue(next.containsKey("B"));
+ assertFalse(next.containsKey("master"));
+
+ orig_r = orig.get("refs/heads/B");
+ next_r = next.get("B");
+ assertEquals(B, orig_r.getObjectId());
+ assertSame("uses cached instance", orig_r, next_r);
+ }
+
+ public void testGetRefs_DiscoversDeletedLoose5() throws IOException {
+ Map<String, Ref> orig, next;
+
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/pu", B);
+ orig = refdir.getRefs(RefDatabase.ALL);
+
+ deleteLooseRef("refs/heads/pu");
+ writeLooseRef("refs/tags/v1.0", v1_0);
+ next = refdir.getRefs(RefDatabase.ALL);
+
+ assertEquals(3, orig.size());
+ assertEquals(3, next.size());
+
+ assertTrue(orig.containsKey("refs/heads/pu"));
+ assertFalse(orig.containsKey("refs/tags/v1.0"));
+ assertFalse(next.containsKey("refs/heads/pu"));
+ assertTrue(next.containsKey("refs/tags/v1.0"));
+ }
+
+ public void testGetRefs_SkipsLockFiles() throws IOException {
+ Map<String, Ref> all;
+
+ writeLooseRef("refs/heads/master", A);
+ writeLooseRef("refs/heads/pu.lock", B);
+ all = refdir.getRefs(RefDatabase.ALL);
+
+ assertEquals(2, all.size());
+
+ assertTrue(all.containsKey(HEAD));
+ assertTrue(all.containsKey("refs/heads/master"));
+ assertFalse(all.containsKey("refs/heads/pu.lock"));
+ }
+
+ public void testGetRefs_CycleInSymbolicRef() throws IOException {
+ Map<String, Ref> all;
+ Ref r;
+
+ writeLooseRef("refs/1", "ref: refs/2\n");
+ writeLooseRef("refs/2", "ref: refs/3\n");
+ writeLooseRef("refs/3", "ref: refs/4\n");
+ writeLooseRef("refs/4", "ref: refs/5\n");
+ writeLooseRef("refs/5", "ref: refs/end\n");
+ writeLooseRef("refs/end", A);
+
+ all = refdir.getRefs(RefDatabase.ALL);
+ 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());
+
+ writeLooseRef("refs/5", "ref: refs/6\n");
+ writeLooseRef("refs/6", "ref: refs/end\n");
+ BUG_WorkAroundRacyGitIssues("refs/5");
+ all = refdir.getRefs(RefDatabase.ALL);
+ r = all.get("refs/1");
+ assertNull("mising 1 due to cycle", r);
+ }
+
+ public void testGetRefs_PackedNotPeeled_Sorted() throws IOException {
+ Map<String, Ref> all;
+
+ writePackedRefs("" + //
+ A.name() + " refs/heads/master\n" + //
+ B.name() + " refs/heads/other\n" + //
+ v1_0.name() + " refs/tags/v1.0\n");
+ all = refdir.getRefs(RefDatabase.ALL);
+
+ assertEquals(4, all.size());
+ final Ref head = all.get(HEAD);
+ final Ref master = all.get("refs/heads/master");
+ final Ref other = all.get("refs/heads/other");
+ final Ref tag = all.get("refs/tags/v1.0");
+
+ assertEquals(A, master.getObjectId());
+ assertFalse(master.isPeeled());
+ assertNull(master.getPeeledObjectId());
+
+ assertEquals(B, other.getObjectId());
+ assertFalse(other.isPeeled());
+ assertNull(other.getPeeledObjectId());
+
+ assertSame(master, head.getTarget());
+ assertEquals(A, head.getObjectId());
+ assertFalse(head.isPeeled());
+ assertNull(head.getPeeledObjectId());
+
+ assertEquals(v1_0, tag.getObjectId());
+ assertFalse(tag.isPeeled());
+ assertNull(tag.getPeeledObjectId());
+ }
+
+ public void testGetRef_PackedNotPeeled_WrongSort() throws IOException {
+ writePackedRefs("" + //
+ v1_0.name() + " refs/tags/v1.0\n" + //
+ B.name() + " refs/heads/other\n" + //
+ A.name() + " refs/heads/master\n");
+
+ final Ref head = refdir.getRef(HEAD);
+ final Ref master = refdir.getRef("refs/heads/master");
+ final Ref other = refdir.getRef("refs/heads/other");
+ final Ref tag = refdir.getRef("refs/tags/v1.0");
+
+ assertEquals(A, master.getObjectId());
+ assertFalse(master.isPeeled());
+ assertNull(master.getPeeledObjectId());
+
+ assertEquals(B, other.getObjectId());
+ assertFalse(other.isPeeled());
+ assertNull(other.getPeeledObjectId());
+
+ assertSame(master, head.getTarget());
+ assertEquals(A, head.getObjectId());
+ assertFalse(head.isPeeled());
+ assertNull(head.getPeeledObjectId());
+
+ assertEquals(v1_0, tag.getObjectId());
+ assertFalse(tag.isPeeled());
+ assertNull(tag.getPeeledObjectId());
+ }
+
+ public void testGetRefs_PackedWithPeeled() throws IOException {
+ Map<String, Ref> all;
+
+ writePackedRefs("# pack-refs with: peeled \n" + //
+ A.name() + " refs/heads/master\n" + //
+ B.name() + " refs/heads/other\n" + //
+ v1_0.name() + " refs/tags/v1.0\n" + //
+ "^" + v1_0.getObject().name() + "\n");
+ all = refdir.getRefs(RefDatabase.ALL);
+
+ assertEquals(4, all.size());
+ final Ref head = all.get(HEAD);
+ final Ref master = all.get("refs/heads/master");
+ final Ref other = all.get("refs/heads/other");
+ final Ref tag = all.get("refs/tags/v1.0");
+
+ assertEquals(A, master.getObjectId());
+ assertTrue(master.isPeeled());
+ assertNull(master.getPeeledObjectId());
+
+ assertEquals(B, other.getObjectId());
+ assertTrue(other.isPeeled());
+ assertNull(other.getPeeledObjectId());
+
+ assertSame(master, head.getTarget());
+ assertEquals(A, head.getObjectId());
+ assertTrue(head.isPeeled());
+ assertNull(head.getPeeledObjectId());
+
+ assertEquals(v1_0, tag.getObjectId());
+ assertTrue(tag.isPeeled());
+ assertEquals(v1_0.getObject(), tag.getPeeledObjectId());
+ }
+
+ public void testGetRef_EmptyDatabase() throws IOException {
+ Ref r;
+
+ r = refdir.getRef(HEAD);
+ assertTrue(r.isSymbolic());
+ assertSame(LOOSE, r.getStorage());
+ assertEquals("refs/heads/master", r.getTarget().getName());
+ assertSame(NEW, r.getTarget().getStorage());
+ assertNull(r.getTarget().getObjectId());
+
+ assertNull(refdir.getRef("refs/heads/master"));
+ assertNull(refdir.getRef("refs/tags/v1.0"));
+ assertNull(refdir.getRef("FETCH_HEAD"));
+ assertNull(refdir.getRef("NOT.A.REF.NAME"));
+ assertNull(refdir.getRef("master"));
+ assertNull(refdir.getRef("v1.0"));
+ }
+
+ public void testGetRef_FetchHead() throws IOException {
+ // This is an odd special case where we need to make sure we read
+ // exactly the first 40 bytes of the file and nothing further on
+ // that line, or the remainder of the file.
+ write(new File(diskRepo.getDirectory(), "FETCH_HEAD"), A.name()
+ + "\tnot-for-merge"
+ + "\tbranch 'master' of git://egit.eclipse.org/jgit\n");
+
+ Ref r = refdir.getRef("FETCH_HEAD");
+ assertFalse(r.isSymbolic());
+ assertEquals(A, r.getObjectId());
+ assertEquals("FETCH_HEAD", r.getName());
+ assertFalse(r.isPeeled());
+ assertNull(r.getPeeledObjectId());
+ }
+
+ public void testGetRef_AnyHeadWithGarbage() throws IOException {
+ write(new File(diskRepo.getDirectory(), "refs/heads/A"), A.name()
+ + "012345 . this is not a standard reference\n"
+ + "#and even more junk\n");
+
+ Ref r = refdir.getRef("refs/heads/A");
+ assertFalse(r.isSymbolic());
+ assertEquals(A, r.getObjectId());
+ assertEquals("refs/heads/A", r.getName());
+ assertFalse(r.isPeeled());
+ assertNull(r.getPeeledObjectId());
+ }
+
+ public void testGetRefs_CorruptSymbolicReference() throws IOException {
+ String name = "refs/heads/A";
+ writeLooseRef(name, "ref: \n");
+ assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty());
+ }
+
+ public void testGetRef_CorruptSymbolicReference() throws IOException {
+ String name = "refs/heads/A";
+ writeLooseRef(name, "ref: \n");
+ try {
+ refdir.getRef(name);
+ fail("read an invalid reference");
+ } catch (IOException err) {
+ String msg = err.getMessage();
+ assertEquals("Not a ref: " + name + ": ref:", msg);
+ }
+ }
+
+ public void testGetRefs_CorruptObjectIdReference() throws IOException {
+ String name = "refs/heads/A";
+ String content = "zoo" + A.name();
+ writeLooseRef(name, content + "\n");
+ assertTrue(refdir.getRefs(RefDatabase.ALL).isEmpty());
+ }
+
+ public void testGetRef_CorruptObjectIdReference() throws IOException {
+ String name = "refs/heads/A";
+ String content = "zoo" + A.name();
+ writeLooseRef(name, content + "\n");
+ try {
+ refdir.getRef(name);
+ fail("read an invalid reference");
+ } catch (IOException err) {
+ String msg = err.getMessage();
+ assertEquals("Not a ref: " + name + ": " + content, msg);
+ }
+ }
+
+ public void testIsNameConflicting() throws IOException {
+ writeLooseRef("refs/heads/a/b", A);
+ writePackedRef("refs/heads/q", B);
+
+ // new references cannot replace an existing container
+ assertTrue(refdir.isNameConflicting("refs"));
+ assertTrue(refdir.isNameConflicting("refs/heads"));
+ assertTrue(refdir.isNameConflicting("refs/heads/a"));
+
+ // existing reference is not conflicting
+ assertFalse(refdir.isNameConflicting("refs/heads/a/b"));
+
+ // new references are not conflicting
+ assertFalse(refdir.isNameConflicting("refs/heads/a/d"));
+ assertFalse(refdir.isNameConflicting("refs/heads/master"));
+
+ // existing reference must not be used as a container
+ assertTrue(refdir.isNameConflicting("refs/heads/a/b/c"));
+ assertTrue(refdir.isNameConflicting("refs/heads/q/master"));
+ }
+
+ public void testPeelLooseTag() throws IOException {
+ writeLooseRef("refs/tags/v1_0", v1_0);
+ writeLooseRef("refs/tags/current", "ref: refs/tags/v1_0\n");
+
+ final Ref tag = refdir.getRef("refs/tags/v1_0");
+ final Ref cur = refdir.getRef("refs/tags/current");
+
+ assertEquals(v1_0, tag.getObjectId());
+ assertFalse(tag.isSymbolic());
+ assertFalse(tag.isPeeled());
+ assertNull(tag.getPeeledObjectId());
+
+ assertEquals(v1_0, cur.getObjectId());
+ assertTrue(cur.isSymbolic());
+ assertFalse(cur.isPeeled());
+ assertNull(cur.getPeeledObjectId());
+
+ final Ref tag_p = refdir.peel(tag);
+ final Ref cur_p = refdir.peel(cur);
+
+ assertNotSame(tag, tag_p);
+ assertFalse(tag_p.isSymbolic());
+ assertTrue(tag_p.isPeeled());
+ assertEquals(v1_0, tag_p.getObjectId());
+ assertEquals(v1_0.getObject(), tag_p.getPeeledObjectId());
+ assertSame(tag_p, refdir.peel(tag_p));
+
+ assertNotSame(cur, cur_p);
+ assertEquals("refs/tags/current", cur_p.getName());
+ assertTrue(cur_p.isSymbolic());
+ assertEquals("refs/tags/v1_0", cur_p.getTarget().getName());
+ assertTrue(cur_p.isPeeled());
+ assertEquals(v1_0, cur_p.getObjectId());
+ assertEquals(v1_0.getObject(), cur_p.getPeeledObjectId());
+
+ // reuses cached peeling later, but not immediately due to
+ // the implementation so we have to fetch it once.
+ final Ref tag_p2 = refdir.getRef("refs/tags/v1_0");
+ assertFalse(tag_p2.isSymbolic());
+ assertTrue(tag_p2.isPeeled());
+ assertEquals(v1_0, tag_p2.getObjectId());
+ assertEquals(v1_0.getObject(), tag_p2.getPeeledObjectId());
+
+ assertSame(tag_p2, refdir.getRef("refs/tags/v1_0"));
+ assertSame(tag_p2, refdir.getRef("refs/tags/current").getTarget());
+ assertSame(tag_p2, refdir.peel(tag_p2));
+ }
+
+ public void testPeelCommit() throws IOException {
+ writeLooseRef("refs/heads/master", A);
+
+ Ref master = refdir.getRef("refs/heads/master");
+ assertEquals(A, master.getObjectId());
+ assertFalse(master.isPeeled());
+ assertNull(master.getPeeledObjectId());
+
+ Ref master_p = refdir.peel(master);
+ assertNotSame(master, master_p);
+ assertEquals(A, master_p.getObjectId());
+ assertTrue(master_p.isPeeled());
+ assertNull(master_p.getPeeledObjectId());
+
+ // reuses cached peeling later, but not immediately due to
+ // the implementation so we have to fetch it once.
+ Ref master_p2 = refdir.getRef("refs/heads/master");
+ assertNotSame(master, master_p2);
+ assertEquals(A, master_p2.getObjectId());
+ assertTrue(master_p2.isPeeled());
+ assertNull(master_p2.getPeeledObjectId());
+ assertSame(master_p2, refdir.peel(master_p2));
+ }
+
+ private void writeLooseRef(String name, AnyObjectId id) throws IOException {
+ writeLooseRef(name, id.name() + "\n");
+ }
+
+ private void writeLooseRef(String name, String content) throws IOException {
+ write(new File(diskRepo.getDirectory(), name), content);
+ }
+
+ private void writePackedRef(String name, AnyObjectId id) throws IOException {
+ writePackedRefs(id.name() + " " + name + "\n");
+ }
+
+ private void writePackedRefs(String content) throws IOException {
+ File pr = new File(diskRepo.getDirectory(), "packed-refs");
+ write(pr, content);
+ }
+
+ private void deleteLooseRef(String name) {
+ File path = new File(diskRepo.getDirectory(), name);
+ assertTrue("deleted " + name, path.delete());
+ }
+
+ /**
+ * Kick the timestamp of a local file.
+ * <p>
+ * We shouldn't have to make these method calls. The cache is using file
+ * system timestamps, and on many systems unit tests run faster than the
+ * modification clock. Dumping the cache after we make an edit behind
+ * RefDirectory's back allows the tests to pass.
+ *
+ * @param name
+ * the file in the repository to force a time change on.
+ */
+ private void BUG_WorkAroundRacyGitIssues(String name) {
+ File path = new File(diskRepo.getDirectory(), name);
+ long old = path.lastModified();
+ long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
+ path.setLastModified(set);
+ assertTrue("time changed", old != path.lastModified());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
index 67ed6abb21..100b758e65 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2009-2010, Google Inc.
* Copyright (C) 2009, Robin Rosenberg
* Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
* and other copyright owners as documented in the project's IP log.
@@ -58,15 +59,26 @@ import org.eclipse.jgit.lib.RefUpdate.Result;
*/
public class RefTest extends SampleDataRepositoryTestCase {
+ private void writeSymref(String src, String dst) throws IOException {
+ RefUpdate u = db.updateRef(src);
+ switch (u.link(dst)) {
+ case NEW:
+ case FORCED:
+ case NO_CHANGE:
+ break;
+ default:
+ fail("link " + src + " to " + dst);
+ }
+ }
+
public void testReadAllIncludingSymrefs() throws Exception {
ObjectId masterId = db.resolve("refs/heads/master");
RefUpdate updateRef = db.updateRef("refs/remotes/origin/master");
updateRef.setNewObjectId(masterId);
updateRef.setForceUpdate(true);
updateRef.update();
- db
- .writeSymref("refs/remotes/origin/HEAD",
- "refs/remotes/origin/master");
+ writeSymref("refs/remotes/origin/HEAD",
+ "refs/remotes/origin/master");
ObjectId r = db.resolve("refs/remotes/origin/HEAD");
assertEquals(masterId, r);
@@ -75,7 +87,7 @@ public class RefTest extends SampleDataRepositoryTestCase {
Ref refHEAD = allRefs.get("refs/remotes/origin/HEAD");
assertNotNull(refHEAD);
assertEquals(masterId, refHEAD.getObjectId());
- assertTrue(refHEAD.isPeeled());
+ assertFalse(refHEAD.isPeeled());
assertNull(refHEAD.getPeeledObjectId());
Ref refmaster = allRefs.get("refs/remotes/origin/master");
@@ -85,9 +97,13 @@ public class RefTest extends SampleDataRepositoryTestCase {
}
public void testReadSymRefToPacked() throws IOException {
- db.writeSymref("HEAD", "refs/heads/b");
+ writeSymref("HEAD", "refs/heads/b");
Ref ref = db.getRef("HEAD");
- assertEquals(Ref.Storage.LOOSE_PACKED, ref.getStorage());
+ assertEquals(Ref.Storage.LOOSE, ref.getStorage());
+ assertTrue("is symref", ref.isSymbolic());
+ ref = ref.getTarget();
+ assertEquals("refs/heads/b", ref.getName());
+ assertEquals(Ref.Storage.PACKED, ref.getStorage());
}
public void testReadSymRefToLoosePacked() throws IOException {
@@ -98,9 +114,12 @@ public class RefTest extends SampleDataRepositoryTestCase {
Result update = updateRef.update();
assertEquals(Result.FORCED, update); // internal
- db.writeSymref("HEAD", "refs/heads/master");
+ writeSymref("HEAD", "refs/heads/master");
Ref ref = db.getRef("HEAD");
- assertEquals(Ref.Storage.LOOSE_PACKED, ref.getStorage());
+ assertEquals(Ref.Storage.LOOSE, ref.getStorage());
+ ref = ref.getTarget();
+ assertEquals("refs/heads/master", ref.getName());
+ assertEquals(Ref.Storage.LOOSE, ref.getStorage());
}
public void testReadLooseRef() throws IOException {
@@ -129,7 +148,7 @@ public class RefTest extends SampleDataRepositoryTestCase {
os.close();
ref = db.getRef("refs/heads/master");
- assertEquals(Storage.LOOSE_PACKED, ref.getStorage());
+ assertEquals(Storage.LOOSE, ref.getStorage());
}
/**
@@ -149,18 +168,26 @@ public class RefTest extends SampleDataRepositoryTestCase {
assertEquals(Result.FORCED, update);
ref = db.getRef("refs/heads/master");
- assertEquals(Storage.LOOSE_PACKED, ref.getStorage());
+ assertEquals(Storage.LOOSE, ref.getStorage());
}
- public void testOrigResolvedNamesBranch() throws IOException {
+ public void testResolvedNamesBranch() throws IOException {
Ref ref = db.getRef("a");
assertEquals("refs/heads/a", ref.getName());
- assertEquals("refs/heads/a", ref.getOrigName());
}
- public void testOrigResolvedNamesSymRef() throws IOException {
- Ref ref = db.getRef("HEAD");
- assertEquals("refs/heads/master", ref.getName());
- assertEquals("HEAD", ref.getOrigName());
+ public void testResolvedSymRef() throws IOException {
+ Ref ref = db.getRef(Constants.HEAD);
+ assertEquals(Constants.HEAD, ref.getName());
+ assertTrue("is symbolic ref", ref.isSymbolic());
+ assertSame(Ref.Storage.LOOSE, ref.getStorage());
+
+ Ref dst = ref.getTarget();
+ assertNotNull("has target", dst);
+ assertEquals("refs/heads/master", dst.getName());
+
+ assertSame(dst.getObjectId(), ref.getObjectId());
+ assertSame(dst.getPeeledObjectId(), ref.getPeeledObjectId());
+ assertEquals(dst.isPeeled(), ref.isPeeled());
}
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java
index d851528cdd..c8a5b33f68 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
+ * Copyright (C) 2009-2010, Google Inc.
* Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
* and other copyright owners as documented in the project's IP log.
*
@@ -56,6 +57,18 @@ import org.eclipse.jgit.revwalk.RevWalk;
public class RefUpdateTest extends SampleDataRepositoryTestCase {
+ private void writeSymref(String src, String dst) throws IOException {
+ RefUpdate u = db.updateRef(src);
+ switch (u.link(dst)) {
+ case NEW:
+ case FORCED:
+ case NO_CHANGE:
+ break;
+ default:
+ fail("link " + src + " to " + dst);
+ }
+ }
+
private RefUpdate updateRef(final String name) throws IOException {
final RefUpdate ref = db.updateRef(name);
ref.setNewObjectId(db.resolve(Constants.HEAD));
@@ -260,10 +273,10 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
delete(ref, Result.FORCED);
}
- public void testRefKeySameAsOrigName() {
+ public void testRefKeySameAsName() {
Map<String, Ref> allRefs = db.getAllRefs();
for (Entry<String, Ref> e : allRefs.entrySet()) {
- assertEquals(e.getKey(), e.getValue().getOrigName());
+ assertEquals(e.getKey(), e.getValue().getName());
}
}
@@ -308,7 +321,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals(ppid, db.resolve("HEAD"));
Ref ref = db.getRef("HEAD");
assertEquals("HEAD", ref.getName());
- assertEquals("HEAD", ref.getOrigName());
+ assertTrue("is detached", !ref.isSymbolic());
// the branch HEAD referred to is left untouched
assertEquals(pid, db.resolve("refs/heads/master"));
@@ -328,7 +341,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
*/
public void testUpdateRefDetachedUnbornHead() throws Exception {
ObjectId ppid = db.resolve("refs/heads/master^");
- db.writeSymref("HEAD", "refs/heads/unborn");
+ writeSymref("HEAD", "refs/heads/unborn");
RefUpdate updateRef = db.updateRef("HEAD", true);
updateRef.setForceUpdate(true);
updateRef.setNewObjectId(ppid);
@@ -337,7 +350,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals(ppid, db.resolve("HEAD"));
Ref ref = db.getRef("HEAD");
assertEquals("HEAD", ref.getName());
- assertEquals("HEAD", ref.getOrigName());
+ assertTrue("is detached", !ref.isSymbolic());
// the branch HEAD referred to is left untouched
assertNull(db.resolve("refs/heads/unborn"));
@@ -414,11 +427,14 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
updateRef.setNewObjectId(oldValue);
update = updateRef.update();
assertEquals(Result.FAST_FORWARD, update);
+
allRefs = db.getAllRefs();
- assertEquals("refs/heads/master", allRefs.get("refs/heads/master").getName());
- assertEquals("refs/heads/master", allRefs.get("refs/heads/master").getOrigName());
- assertEquals("refs/heads/master", allRefs.get("HEAD").getName());
- assertEquals("HEAD", allRefs.get("HEAD").getOrigName());
+ Ref master = allRefs.get("refs/heads/master");
+ Ref head = allRefs.get("HEAD");
+ assertEquals("refs/heads/master", master.getName());
+ assertEquals("HEAD", head.getName());
+ assertTrue("is symbolic reference", head.isSymbolic());
+ assertSame(master, head.getTarget());
}
/**
@@ -430,21 +446,24 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
*
* @throws Exception
*/
- public void testRefsCacheAfterUpdateLoosOnly() throws Exception {
+ public void testRefsCacheAfterUpdateLooseOnly() throws Exception {
// Do not use the defalt repo for this case.
Map<String, Ref> allRefs = db.getAllRefs();
ObjectId oldValue = db.resolve("HEAD");
- db.writeSymref(Constants.HEAD, "refs/heads/newref");
+ writeSymref(Constants.HEAD, "refs/heads/newref");
RefUpdate updateRef = db.updateRef(Constants.HEAD);
updateRef.setForceUpdate(true);
updateRef.setNewObjectId(oldValue);
Result update = updateRef.update();
assertEquals(Result.NEW, update);
+
allRefs = db.getAllRefs();
- assertEquals("refs/heads/newref", allRefs.get("HEAD").getName());
- assertEquals("HEAD", allRefs.get("HEAD").getOrigName());
- assertEquals("refs/heads/newref", allRefs.get("refs/heads/newref").getName());
- assertEquals("refs/heads/newref", allRefs.get("refs/heads/newref").getOrigName());
+ Ref head = allRefs.get("HEAD");
+ Ref newref = allRefs.get("refs/heads/newref");
+ assertEquals("refs/heads/newref", newref.getName());
+ assertEquals("HEAD", head.getName());
+ assertTrue("is symbolic reference", head.isSymbolic());
+ assertSame(newref, head.getTarget());
}
/**
@@ -575,8 +594,8 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
ObjectId oldHead = db.resolve(Constants.HEAD);
assertFalse("precondition for this test, branch b != HEAD", rb
.equals(oldHead));
- RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
- assertTrue("no log on old branch", new File(db.getDirectory(),
+ writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
+ assertTrue("log on old branch", new File(db.getDirectory(),
"logs/refs/heads/b").exists());
RefRename renameRef = db.renameRef("refs/heads/b",
"refs/heads/new/name");
@@ -595,11 +614,11 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
public void testRenameCurrentBranch() throws IOException {
ObjectId rb = db.resolve("refs/heads/b");
- db.writeSymref(Constants.HEAD, "refs/heads/b");
+ writeSymref(Constants.HEAD, "refs/heads/b");
ObjectId oldHead = db.resolve(Constants.HEAD);
assertTrue("internal test condition, b == HEAD", rb.equals(oldHead));
- RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
- assertTrue("no log on old branch", new File(db.getDirectory(),
+ writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
+ assertTrue("log on old branch", new File(db.getDirectory(),
"logs/refs/heads/b").exists());
RefRename renameRef = db.renameRef("refs/heads/b",
"refs/heads/new/name");
@@ -625,10 +644,9 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
updateRef.setForceUpdate(true);
Result update = updateRef.update();
assertEquals("internal check new ref is loose", Result.FORCED, update);
- assertEquals(Ref.Storage.LOOSE_PACKED, db.getRef("refs/heads/b")
- .getStorage());
- RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
- assertTrue("no log on old branch", new File(db.getDirectory(),
+ assertEquals(Ref.Storage.LOOSE, db.getRef("refs/heads/b").getStorage());
+ writeReflog(db, rb, rb, "Just a message", "refs/heads/b");
+ assertTrue("log on old branch", new File(db.getDirectory(),
"logs/refs/heads/b").exists());
RefRename renameRef = db.renameRef("refs/heads/b",
"refs/heads/new/name");
@@ -654,10 +672,10 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
public void tryRenameWhenLocked(String toLock, String fromName,
String toName, String headPointsTo) throws IOException {
// setup
- db.writeSymref(Constants.HEAD, headPointsTo);
+ writeSymref(Constants.HEAD, headPointsTo);
ObjectId oldfromId = db.resolve(fromName);
ObjectId oldHeadId = db.resolve(Constants.HEAD);
- RefLogWriter.writeReflog(db, oldfromId, oldfromId, "Just a message",
+ writeReflog(db, oldfromId, oldfromId, "Just a message",
fromName);
List<org.eclipse.jgit.lib.ReflogReader.Entry> oldFromLog = db
.getReflogReader(fromName).getReverseEntries();
@@ -691,8 +709,8 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals(oldFromLog.toString(), db.getReflogReader(fromName)
.getReverseEntries().toString());
if (oldHeadId != null)
- assertEquals(oldHeadLog, db.getReflogReader(Constants.HEAD)
- .getReverseEntries());
+ assertEquals(oldHeadLog.toString(), db.getReflogReader(
+ Constants.HEAD).getReverseEntries().toString());
} finally {
lockFile.unlock();
}
@@ -733,12 +751,6 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
"refs/heads/new/name", "refs/heads/new/name");
}
- public void testRenameBranchCannotLockAFileHEADisToLockTmp()
- throws IOException {
- tryRenameWhenLocked("RENAMED-REF.." + Thread.currentThread().getId(),
- "refs/heads/b", "refs/heads/new/name", "refs/heads/new/name");
- }
-
public void testRenameBranchCannotLockAFileHEADisOtherLockFrom()
throws IOException {
tryRenameWhenLocked("refs/heads/b", "refs/heads/b",
@@ -751,23 +763,17 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
"refs/heads/new/name", "refs/heads/a");
}
- public void testRenameBranchCannotLockAFileHEADisOtherLockTmp()
- throws IOException {
- tryRenameWhenLocked("RENAMED-REF.." + Thread.currentThread().getId(),
- "refs/heads/b", "refs/heads/new/name", "refs/heads/a");
- }
-
public void testRenameRefNameColission1avoided() throws IOException {
// setup
ObjectId rb = db.resolve("refs/heads/b");
- db.writeSymref(Constants.HEAD, "refs/heads/a");
+ writeSymref(Constants.HEAD, "refs/heads/a");
RefUpdate updateRef = db.updateRef("refs/heads/a");
updateRef.setNewObjectId(rb);
updateRef.setRefLogMessage("Setup", false);
assertEquals(Result.FAST_FORWARD, updateRef.update());
ObjectId oldHead = db.resolve(Constants.HEAD);
assertTrue(rb.equals(oldHead)); // assumption for this test
- RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/a");
+ writeReflog(db, rb, rb, "Just a message", "refs/heads/a");
assertTrue("internal check, we have a log", new File(db.getDirectory(),
"logs/refs/heads/a").exists());
@@ -792,7 +798,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
public void testRenameRefNameColission2avoided() throws IOException {
// setup
ObjectId rb = db.resolve("refs/heads/b");
- db.writeSymref(Constants.HEAD, "refs/heads/prefix/a");
+ writeSymref(Constants.HEAD, "refs/heads/prefix/a");
RefUpdate updateRef = db.updateRef("refs/heads/prefix/a");
updateRef.setNewObjectId(rb);
updateRef.setRefLogMessage("Setup", false);
@@ -800,7 +806,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals(Result.FORCED, updateRef.update());
ObjectId oldHead = db.resolve(Constants.HEAD);
assertTrue(rb.equals(oldHead)); // assumption for this test
- RefLogWriter.writeReflog(db, rb, rb, "Just a message",
+ writeReflog(db, rb, rb, "Just a message",
"refs/heads/prefix/a");
assertTrue("internal check, we have a log", new File(db.getDirectory(),
"logs/refs/heads/prefix/a").exists());
@@ -823,4 +829,13 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader(
"HEAD").getReverseEntries().get(0).getComment());
}
+
+ private void writeReflog(Repository db, ObjectId oldId, ObjectId newId,
+ String msg, String refName) throws IOException {
+ RefDirectory refs = (RefDirectory) db.getRefDatabase();
+ RefDirectoryUpdate update = refs.newUpdate(refName, true);
+ update.setOldObjectId(oldId);
+ update.setNewObjectId(newId);
+ refs.log(update, msg, true);
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
index 8e5f6fc83b..88bcf76710 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
* Copyright (C) 2009, Christian Halstrick, Matthias Sohn, SAP AG
- * Copyright (C) 2009, Google Inc.
+ * Copyright (C) 2009-2010, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -54,7 +54,7 @@ public class ReflogConfigTest extends RepositoryTestCase {
// check that there are no entries in the reflog and turn off writing
// reflogs
- assertNull(db.getReflogReader(Constants.HEAD));
+ assertEquals(0, db.getReflogReader(Constants.HEAD).getReverseEntries().size());
db.getConfig().setBoolean("core", null, "logallrefupdates", false);
// do one commit and check that reflog size is 0: no reflogs should be
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java
new file mode 100644
index 0000000000..bff4fc34fa
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2010, 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;
+
+import junit.framework.TestCase;
+
+public class SymbolicRefTest extends TestCase {
+ private static final ObjectId ID_A = ObjectId
+ .fromString("41eb0d88f833b558bddeb269b7ab77399cdf98ed");
+
+ private static final ObjectId ID_B = ObjectId
+ .fromString("698dd0b8d0c299f080559a1cffc7fe029479a408");
+
+ private static final String targetName = "refs/heads/a.test.ref";
+
+ private static final String name = "refs/remotes/origin/HEAD";
+
+ public void testConstructor() {
+ Ref t;
+ SymbolicRef r;
+
+ t = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, targetName, null);
+ r = new SymbolicRef(name, t);
+ assertSame(Ref.Storage.LOOSE, r.getStorage());
+ assertSame(name, r.getName());
+ assertNull("no id on new ref", r.getObjectId());
+ assertFalse("not peeled", r.isPeeled());
+ assertNull("no peel id", r.getPeeledObjectId());
+ assertSame("leaf is t", t, r.getLeaf());
+ assertSame("target is t", t, r.getTarget());
+ assertTrue("is symbolic", r.isSymbolic());
+
+ t = new ObjectIdRef.Unpeeled(Ref.Storage.PACKED, targetName, ID_A);
+ r = new SymbolicRef(name, t);
+ assertSame(Ref.Storage.LOOSE, r.getStorage());
+ assertSame(name, r.getName());
+ assertSame(ID_A, r.getObjectId());
+ assertFalse("not peeled", r.isPeeled());
+ assertNull("no peel id", r.getPeeledObjectId());
+ assertSame("leaf is t", t, r.getLeaf());
+ assertSame("target is t", t, r.getTarget());
+ assertTrue("is symbolic", r.isSymbolic());
+ }
+
+ public void testLeaf() {
+ Ref a;
+ SymbolicRef b, c, d;
+
+ a = new ObjectIdRef.PeeledTag(Ref.Storage.PACKED, targetName, ID_A, ID_B);
+ b = new SymbolicRef("B", a);
+ c = new SymbolicRef("C", b);
+ d = new SymbolicRef("D", c);
+
+ assertSame(c, d.getTarget());
+ assertSame(b, c.getTarget());
+ assertSame(a, b.getTarget());
+
+ assertSame(a, d.getLeaf());
+ assertSame(a, c.getLeaf());
+ assertSame(a, b.getLeaf());
+ assertSame(a, a.getLeaf());
+
+ assertSame(ID_A, d.getObjectId());
+ assertSame(ID_A, c.getObjectId());
+ assertSame(ID_A, b.getObjectId());
+
+ assertTrue(d.isPeeled());
+ assertTrue(c.isPeeled());
+ assertTrue(b.isPeeled());
+
+ assertSame(ID_B, d.getPeeledObjectId());
+ assertSame(ID_B, c.getPeeledObjectId());
+ assertSame(ID_B, b.getPeeledObjectId());
+ }
+
+ public void testToString() {
+ Ref a;
+ SymbolicRef b, c, d;
+
+ a = new ObjectIdRef.PeeledTag(Ref.Storage.PACKED, targetName, ID_A, ID_B);
+ b = new SymbolicRef("B", a);
+ c = new SymbolicRef("C", b);
+ d = new SymbolicRef("D", c);
+
+ assertEquals("SymbolicRef[D -> C -> B -> " + targetName + "="
+ + ID_A.name() + "]", d.toString());
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java
index e0cc345103..ce8a79ef96 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java
@@ -543,6 +543,7 @@ public class T0003_Basic extends SampleDataRepositoryTestCase {
w.println("0ce2ebdb36076ef0b38adbe077a07d43b43e3807 refs/tags/test022");
w.println("^b5d3b45a96b340441f5abb9080411705c51cc86c");
w.close();
+ ((RefDirectory)db.getRefDatabase()).rescan();
Tag mapTag20 = db.mapTag("test020");
assertNotNull("have tag test020", mapTag20);
@@ -673,6 +674,8 @@ public class T0003_Basic extends SampleDataRepositoryTestCase {
public void test028_LockPackedRef() throws IOException {
writeTrashFile(".git/packed-refs", "7f822839a2fe9760f386cbbbcb3f92c5fe81def7 refs/heads/foobar");
writeTrashFile(".git/HEAD", "ref: refs/heads/foobar\n");
+ BUG_WorkAroundRacyGitIssues("packed-refs");
+ BUG_WorkAroundRacyGitIssues("HEAD");
ObjectId resolve = db.resolve("HEAD");
assertEquals("7f822839a2fe9760f386cbbbcb3f92c5fe81def7", resolve.name());
@@ -727,4 +730,23 @@ public class T0003_Basic extends SampleDataRepositoryTestCase {
assertEquals("subdir/File.java", Repository.stripWorkDir(db.getWorkDir(), file));
}
+
+ /**
+ * Kick the timestamp of a local file.
+ * <p>
+ * We shouldn't have to make these method calls. The cache is using file
+ * system timestamps, and on many systems unit tests run faster than the
+ * modification clock. Dumping the cache after we make an edit behind
+ * RefDirectory's back allows the tests to pass.
+ *
+ * @param name
+ * the file in the repository to force a time change on.
+ */
+ private void BUG_WorkAroundRacyGitIssues(String name) {
+ File path = new File(db.getDirectory(), name);
+ long old = path.lastModified();
+ long set = 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009
+ path.setLastModified(set);
+ assertTrue("time changed", old != path.lastModified());
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
index 8e997e3f8c..99edbd98ca 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
@@ -51,6 +51,7 @@ import java.util.Map;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -88,7 +89,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
final RemoteRefUpdate rru = new RemoteRefUpdate(db,
"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
"refs/heads/master", false, null, null);
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
testOneUpdateStatus(rru, ref, Status.OK, true);
}
@@ -103,7 +104,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
final RemoteRefUpdate rru = new RemoteRefUpdate(db,
"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
"refs/heads/master", false, null, null);
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("0000000000000000000000000000000000000001"));
testOneUpdateStatus(rru, ref, Status.REJECTED_NONFASTFORWARD, null);
}
@@ -118,7 +119,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
final RemoteRefUpdate rru = new RemoteRefUpdate(db,
"ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
"refs/heads/master", false, null, null);
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
testOneUpdateStatus(rru, ref, Status.REJECTED_NONFASTFORWARD, null);
}
@@ -132,7 +133,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
final RemoteRefUpdate rru = new RemoteRefUpdate(db,
"ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
"refs/heads/master", true, null, null);
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
testOneUpdateStatus(rru, ref, Status.OK, false);
}
@@ -157,7 +158,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
public void testUpdateDelete() throws IOException {
final RemoteRefUpdate rru = new RemoteRefUpdate(db, null,
"refs/heads/master", false, null, null);
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
testOneUpdateStatus(rru, ref, Status.OK, true);
}
@@ -183,7 +184,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
final RemoteRefUpdate rru = new RemoteRefUpdate(db,
"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
"refs/heads/master", false, null, null);
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
testOneUpdateStatus(rru, ref, Status.UP_TO_DATE, null);
}
@@ -198,7 +199,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
"refs/heads/master", false, null, ObjectId
.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
testOneUpdateStatus(rru, ref, Status.OK, true);
}
@@ -214,7 +215,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
"refs/heads/master", false, null, ObjectId
.fromString("0000000000000000000000000000000000000001"));
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
testOneUpdateStatus(rru, ref, Status.REJECTED_REMOTE_CHANGED, null);
}
@@ -231,7 +232,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
"refs/heads/master", true, null, ObjectId
.fromString("0000000000000000000000000000000000000001"));
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
testOneUpdateStatus(rru, ref, Status.REJECTED_REMOTE_CHANGED, null);
}
@@ -246,7 +247,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
final RemoteRefUpdate rru = new RemoteRefUpdate(db,
"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
"refs/heads/master", false, null, null);
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
testOneUpdateStatus(rru, ref, Status.REJECTED_OTHER_REASON, null);
}
@@ -260,7 +261,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
public void testUpdateMixedCases() throws IOException {
final RemoteRefUpdate rruOk = new RemoteRefUpdate(db, null,
"refs/heads/master", false, null, null);
- final Ref refToChange = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref refToChange = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
final RemoteRefUpdate rruReject = new RemoteRefUpdate(db, null,
"refs/heads/nonexisting", false, null, null);
@@ -282,7 +283,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
final RemoteRefUpdate rru = new RemoteRefUpdate(db,
"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
"refs/heads/master", false, "refs/remotes/test/master", null);
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
refUpdates.add(rru);
advertisedRefs.add(ref);
@@ -303,7 +304,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
final RemoteRefUpdate rru = new RemoteRefUpdate(db,
"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
"refs/heads/master", false, null, null);
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
refUpdates.add(rru);
advertisedRefs.add(ref);
@@ -320,7 +321,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
final RemoteRefUpdate rru = new RemoteRefUpdate(db,
"ac7e7e44c1885efb472ad54a78327d66bfc4ecef",
"refs/heads/master", false, null, null);
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9"));
final PushResult result = testOneUpdateStatus(rru, ref,
Status.REJECTED_NONFASTFORWARD, null);
@@ -336,7 +337,7 @@ public class PushProcessTest extends SampleDataRepositoryTestCase {
final RemoteRefUpdate rru = new RemoteRefUpdate(db,
"2c349335b7f797072cf729c4f3bb0914ecb6dec9",
"refs/heads/master", false, "refs/remotes/test/master", null);
- final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master",
+ final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
refUpdates.add(rru);
advertisedRefs.add(ref);
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 38dbe0962e..955b0c95ec 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
@@ -46,6 +46,7 @@ package org.eclipse.jgit.transport;
import junit.framework.TestCase;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;
public class RefSpecTest extends TestCase {
@@ -59,12 +60,12 @@ public class RefSpecTest extends TestCase {
assertEquals(sn + ":" + sn, rs.toString());
assertEquals(rs, new RefSpec(rs.toString()));
- Ref r = new Ref(Ref.Storage.LOOSE, sn, null);
+ Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn, null);
assertTrue(rs.matchSource(r));
assertTrue(rs.matchDestination(r));
assertSame(rs, rs.expandFromSource(r));
- r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null);
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn + "-and-more", null);
assertFalse(rs.matchSource(r));
assertFalse(rs.matchDestination(r));
}
@@ -91,12 +92,12 @@ public class RefSpecTest extends TestCase {
assertEquals("+" + sn + ":" + sn, rs.toString());
assertEquals(rs, new RefSpec(rs.toString()));
- Ref r = new Ref(Ref.Storage.LOOSE, sn, null);
+ Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn, null);
assertTrue(rs.matchSource(r));
assertTrue(rs.matchDestination(r));
assertSame(rs, rs.expandFromSource(r));
- r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null);
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn + "-and-more", null);
assertFalse(rs.matchSource(r));
assertFalse(rs.matchDestination(r));
}
@@ -111,12 +112,12 @@ public class RefSpecTest extends TestCase {
assertEquals(sn, rs.toString());
assertEquals(rs, new RefSpec(rs.toString()));
- Ref r = new Ref(Ref.Storage.LOOSE, sn, null);
+ Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn, null);
assertTrue(rs.matchSource(r));
assertFalse(rs.matchDestination(r));
assertSame(rs, rs.expandFromSource(r));
- r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null);
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn + "-and-more", null);
assertFalse(rs.matchSource(r));
assertFalse(rs.matchDestination(r));
}
@@ -131,12 +132,12 @@ public class RefSpecTest extends TestCase {
assertEquals("+" + sn, rs.toString());
assertEquals(rs, new RefSpec(rs.toString()));
- Ref r = new Ref(Ref.Storage.LOOSE, sn, null);
+ Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn, null);
assertTrue(rs.matchSource(r));
assertFalse(rs.matchDestination(r));
assertSame(rs, rs.expandFromSource(r));
- r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null);
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn + "-and-more", null);
assertFalse(rs.matchSource(r));
assertFalse(rs.matchDestination(r));
}
@@ -151,12 +152,12 @@ public class RefSpecTest extends TestCase {
assertEquals(":" + sn, rs.toString());
assertEquals(rs, new RefSpec(rs.toString()));
- Ref r = new Ref(Ref.Storage.LOOSE, sn, null);
+ Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn, null);
assertFalse(rs.matchSource(r));
assertTrue(rs.matchDestination(r));
assertSame(rs, rs.expandFromSource(r));
- r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null);
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, sn + "-and-more", null);
assertFalse(rs.matchSource(r));
assertFalse(rs.matchDestination(r));
}
@@ -175,7 +176,7 @@ public class RefSpecTest extends TestCase {
Ref r;
RefSpec expanded;
- r = new Ref(Ref.Storage.LOOSE, "refs/heads/master", null);
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master", null);
assertTrue(rs.matchSource(r));
assertFalse(rs.matchDestination(r));
expanded = rs.expandFromSource(r);
@@ -185,11 +186,11 @@ public class RefSpecTest extends TestCase {
assertEquals(r.getName(), expanded.getSource());
assertEquals("refs/remotes/origin/master", expanded.getDestination());
- r = new Ref(Ref.Storage.LOOSE, "refs/remotes/origin/next", null);
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/remotes/origin/next", null);
assertFalse(rs.matchSource(r));
assertTrue(rs.matchDestination(r));
- r = new Ref(Ref.Storage.LOOSE, "refs/tags/v1.0", null);
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/tags/v1.0", null);
assertFalse(rs.matchSource(r));
assertFalse(rs.matchDestination(r));
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
new file mode 100644
index 0000000000..c6471ded9b
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2010, 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 java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+
+public class RefListTest extends TestCase {
+ private static final ObjectId ID = ObjectId
+ .fromString("41eb0d88f833b558bddeb269b7ab77399cdf98ed");
+
+ private static final Ref REF_A = newRef("A");
+
+ private static final Ref REF_B = newRef("B");
+
+ private static final Ref REF_c = newRef("c");
+
+ public void testEmpty() {
+ RefList<Ref> list = RefList.emptyList();
+ assertEquals(0, list.size());
+ assertTrue(list.isEmpty());
+ assertFalse(list.iterator().hasNext());
+ assertEquals(-1, list.find("a"));
+ assertEquals(-1, list.find("z"));
+ assertFalse(list.contains("a"));
+ assertNull(list.get("a"));
+ try {
+ list.get(0);
+ fail("RefList.emptyList should have 0 element array");
+ } catch (ArrayIndexOutOfBoundsException err) {
+ // expected
+ }
+ }
+
+ public void testEmptyBuilder() {
+ RefList<Ref> list = new RefList.Builder<Ref>().toRefList();
+ assertEquals(0, list.size());
+ assertFalse(list.iterator().hasNext());
+ assertEquals(-1, list.find("a"));
+ assertEquals(-1, list.find("z"));
+ assertFalse(list.contains("a"));
+ assertNull(list.get("a"));
+ assertTrue(list.asList().isEmpty());
+ assertEquals("[]", list.toString());
+
+ // default array capacity should be 16, with no bounds checking.
+ assertNull(list.get(16 - 1));
+ try {
+ list.get(16);
+ fail("default RefList should have 16 element array");
+ } catch (ArrayIndexOutOfBoundsException err) {
+ // expected
+ }
+ }
+
+ public void testBuilder_AddThenSort() {
+ RefList.Builder<Ref> builder = new RefList.Builder<Ref>(1);
+ builder.add(REF_B);
+ builder.add(REF_A);
+
+ RefList<Ref> list = builder.toRefList();
+ assertEquals(2, list.size());
+ assertSame(REF_B, list.get(0));
+ assertSame(REF_A, list.get(1));
+
+ builder.sort();
+ list = builder.toRefList();
+ assertEquals(2, list.size());
+ assertSame(REF_A, list.get(0));
+ assertSame(REF_B, list.get(1));
+ }
+
+ public void testBuilder_AddAll() {
+ RefList.Builder<Ref> builder = new RefList.Builder<Ref>(1);
+ Ref[] src = { REF_A, REF_B, REF_c, REF_A };
+ builder.addAll(src, 1, 2);
+
+ RefList<Ref> list = builder.toRefList();
+ assertEquals(2, list.size());
+ assertSame(REF_B, list.get(0));
+ assertSame(REF_c, list.get(1));
+ }
+
+ public void testBuilder_Set() {
+ RefList.Builder<Ref> builder = new RefList.Builder<Ref>();
+ builder.add(REF_A);
+ builder.add(REF_A);
+
+ assertEquals(2, builder.size());
+ assertSame(REF_A, builder.get(0));
+ assertSame(REF_A, builder.get(1));
+
+ RefList<Ref> list = builder.toRefList();
+ assertEquals(2, list.size());
+ assertSame(REF_A, list.get(0));
+ assertSame(REF_A, list.get(1));
+ builder.set(1, REF_B);
+
+ list = builder.toRefList();
+ assertEquals(2, list.size());
+ assertSame(REF_A, list.get(0));
+ assertSame(REF_B, list.get(1));
+ }
+
+ public void testBuilder_Remove() {
+ RefList.Builder<Ref> builder = new RefList.Builder<Ref>();
+ builder.add(REF_A);
+ builder.add(REF_B);
+ builder.remove(0);
+
+ assertEquals(1, builder.size());
+ assertSame(REF_B, builder.get(0));
+ }
+
+ public void testSet() {
+ RefList<Ref> one = toList(REF_A, REF_A);
+ RefList<Ref> two = one.set(1, REF_B);
+ assertNotSame(one, two);
+
+ // one is not modified
+ assertEquals(2, one.size());
+ assertSame(REF_A, one.get(0));
+ assertSame(REF_A, one.get(1));
+
+ // but two is
+ assertEquals(2, two.size());
+ assertSame(REF_A, one.get(0));
+ assertSame(REF_B, two.get(1));
+ }
+
+ public void testAddToEmptyList() {
+ RefList<Ref> one = toList();
+ RefList<Ref> two = one.add(0, REF_B);
+ assertNotSame(one, two);
+
+ // one is not modified, but two is
+ assertEquals(0, one.size());
+ assertEquals(1, two.size());
+ assertFalse(two.isEmpty());
+ assertSame(REF_B, two.get(0));
+ }
+
+ public void testAddToFrontOfList() {
+ RefList<Ref> one = toList(REF_A);
+ RefList<Ref> two = one.add(0, REF_B);
+ assertNotSame(one, two);
+
+ // one is not modified, but two is
+ assertEquals(1, one.size());
+ assertSame(REF_A, one.get(0));
+ assertEquals(2, two.size());
+ assertSame(REF_B, two.get(0));
+ assertSame(REF_A, two.get(1));
+ }
+
+ public void testAddToEndOfList() {
+ RefList<Ref> one = toList(REF_A);
+ RefList<Ref> two = one.add(1, REF_B);
+ assertNotSame(one, two);
+
+ // one is not modified, but two is
+ assertEquals(1, one.size());
+ assertSame(REF_A, one.get(0));
+ assertEquals(2, two.size());
+ assertSame(REF_A, two.get(0));
+ assertSame(REF_B, two.get(1));
+ }
+
+ public void testAddToMiddleOfListByInsertionPosition() {
+ RefList<Ref> one = toList(REF_A, REF_c);
+
+ assertEquals(-2, one.find(REF_B.getName()));
+
+ RefList<Ref> two = one.add(one.find(REF_B.getName()), REF_B);
+ assertNotSame(one, two);
+
+ // one is not modified, but two is
+ assertEquals(2, one.size());
+ assertSame(REF_A, one.get(0));
+ assertSame(REF_c, one.get(1));
+
+ assertEquals(3, two.size());
+ assertSame(REF_A, two.get(0));
+ assertSame(REF_B, two.get(1));
+ assertSame(REF_c, two.get(2));
+ }
+
+ public void testPutNewEntry() {
+ RefList<Ref> one = toList(REF_A, REF_c);
+ RefList<Ref> two = one.put(REF_B);
+ assertNotSame(one, two);
+
+ // one is not modified, but two is
+ assertEquals(2, one.size());
+ assertSame(REF_A, one.get(0));
+ assertSame(REF_c, one.get(1));
+
+ assertEquals(3, two.size());
+ assertSame(REF_A, two.get(0));
+ assertSame(REF_B, two.get(1));
+ assertSame(REF_c, two.get(2));
+ }
+
+ public void testPutReplaceEntry() {
+ Ref otherc = newRef(REF_c.getName());
+ assertNotSame(REF_c, otherc);
+
+ RefList<Ref> one = toList(REF_A, REF_c);
+ RefList<Ref> two = one.put(otherc);
+ assertNotSame(one, two);
+
+ // one is not modified, but two is
+ assertEquals(2, one.size());
+ assertSame(REF_A, one.get(0));
+ assertSame(REF_c, one.get(1));
+
+ assertEquals(2, two.size());
+ assertSame(REF_A, two.get(0));
+ assertSame(otherc, two.get(1));
+ }
+
+ public void testRemoveFrontOfList() {
+ RefList<Ref> one = toList(REF_A, REF_B, REF_c);
+ RefList<Ref> two = one.remove(0);
+ assertNotSame(one, two);
+
+ assertEquals(3, one.size());
+ assertSame(REF_A, one.get(0));
+ assertSame(REF_B, one.get(1));
+ assertSame(REF_c, one.get(2));
+
+ assertEquals(2, two.size());
+ assertSame(REF_B, two.get(0));
+ assertSame(REF_c, two.get(1));
+ }
+
+ public void testRemoveMiddleOfList() {
+ RefList<Ref> one = toList(REF_A, REF_B, REF_c);
+ RefList<Ref> two = one.remove(1);
+ assertNotSame(one, two);
+
+ assertEquals(3, one.size());
+ assertSame(REF_A, one.get(0));
+ assertSame(REF_B, one.get(1));
+ assertSame(REF_c, one.get(2));
+
+ assertEquals(2, two.size());
+ assertSame(REF_A, two.get(0));
+ assertSame(REF_c, two.get(1));
+ }
+
+ public void testRemoveEndOfList() {
+ RefList<Ref> one = toList(REF_A, REF_B, REF_c);
+ RefList<Ref> two = one.remove(2);
+ assertNotSame(one, two);
+
+ assertEquals(3, one.size());
+ assertSame(REF_A, one.get(0));
+ assertSame(REF_B, one.get(1));
+ assertSame(REF_c, one.get(2));
+
+ assertEquals(2, two.size());
+ assertSame(REF_A, two.get(0));
+ assertSame(REF_B, two.get(1));
+ }
+
+ public void testRemoveMakesEmpty() {
+ RefList<Ref> one = toList(REF_A);
+ RefList<Ref> two = one.remove(1);
+ assertNotSame(one, two);
+ assertSame(two, RefList.emptyList());
+ }
+
+ public void testToString() {
+ StringBuilder exp = new StringBuilder();
+ exp.append("[");
+ exp.append(REF_A);
+ exp.append(", ");
+ exp.append(REF_B);
+ exp.append("]");
+
+ RefList<Ref> list = toList(REF_A, REF_B);
+ assertEquals(exp.toString(), list.toString());
+ }
+
+ public void testBuilder_ToString() {
+ StringBuilder exp = new StringBuilder();
+ exp.append("[");
+ exp.append(REF_A);
+ exp.append(", ");
+ exp.append(REF_B);
+ exp.append("]");
+
+ RefList.Builder<Ref> list = new RefList.Builder<Ref>();
+ list.add(REF_A);
+ list.add(REF_B);
+ assertEquals(exp.toString(), list.toString());
+ }
+
+ public void testFindContainsGet() {
+ RefList<Ref> list = toList(REF_A, REF_B, REF_c);
+
+ assertEquals(0, list.find("A"));
+ assertEquals(1, list.find("B"));
+ assertEquals(2, list.find("c"));
+
+ assertEquals(-1, list.find("0"));
+ assertEquals(-2, list.find("AB"));
+ assertEquals(-3, list.find("a"));
+ assertEquals(-4, list.find("z"));
+
+ assertSame(REF_A, list.get("A"));
+ assertSame(REF_B, list.get("B"));
+ assertSame(REF_c, list.get("c"));
+ assertNull(list.get("AB"));
+ assertNull(list.get("z"));
+
+ assertTrue(list.contains("A"));
+ assertTrue(list.contains("B"));
+ assertTrue(list.contains("c"));
+ assertFalse(list.contains("AB"));
+ assertFalse(list.contains("z"));
+ }
+
+ public void testIterable() {
+ RefList<Ref> list = toList(REF_A, REF_B, REF_c);
+
+ int idx = 0;
+ for (Ref ref : list)
+ assertSame(list.get(idx++), ref);
+ assertEquals(3, idx);
+
+ Iterator<Ref> i = RefList.emptyList().iterator();
+ try {
+ i.next();
+ fail("did not throw NoSuchElementException");
+ } catch (NoSuchElementException err) {
+ // expected
+ }
+
+ i = list.iterator();
+ assertTrue(i.hasNext());
+ assertSame(REF_A, i.next());
+ try {
+ i.remove();
+ fail("did not throw UnsupportedOperationException");
+ } catch (UnsupportedOperationException err) {
+ // expected
+ }
+ }
+
+ public void testCopyLeadingPrefix() {
+ RefList<Ref> one = toList(REF_A, REF_B, REF_c);
+ RefList<Ref> two = one.copy(2).toRefList();
+ assertNotSame(one, two);
+
+ assertEquals(3, one.size());
+ assertSame(REF_A, one.get(0));
+ assertSame(REF_B, one.get(1));
+ assertSame(REF_c, one.get(2));
+
+ assertEquals(2, two.size());
+ assertSame(REF_A, two.get(0));
+ assertSame(REF_B, two.get(1));
+ }
+
+ public void testCopyConstructorReusesArray() {
+ RefList.Builder<Ref> one = new RefList.Builder<Ref>();
+ one.add(REF_A);
+
+ RefList<Ref> two = new RefList<Ref>(one.toRefList());
+ one.set(0, REF_B);
+ assertSame(REF_B, two.get(0));
+ }
+
+ private RefList<Ref> toList(Ref... refs) {
+ RefList.Builder<Ref> b = new RefList.Builder<Ref>(refs.length);
+ b.addAll(refs, 0, refs.length);
+ return b.toRefList();
+ }
+
+ private static Ref newRef(final String name) {
+ return new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
new file mode 100644
index 0000000000..c4c2383f5c
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2010, 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 java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.SymbolicRef;
+
+public class RefMapTest extends TestCase {
+ private static final ObjectId ID_ONE = ObjectId
+ .fromString("41eb0d88f833b558bddeb269b7ab77399cdf98ed");
+
+ private static final ObjectId ID_TWO = ObjectId
+ .fromString("698dd0b8d0c299f080559a1cffc7fe029479a408");
+
+ private RefList<Ref> packed;
+
+ private RefList<Ref> loose;
+
+ private RefList<Ref> resolved;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ packed = RefList.emptyList();
+ loose = RefList.emptyList();
+ resolved = RefList.emptyList();
+ }
+
+ public void testEmpty_NoPrefix1() {
+ RefMap map = new RefMap("", packed, loose, resolved);
+ assertTrue(map.isEmpty()); // before size was computed
+ assertEquals(0, map.size());
+ assertTrue(map.isEmpty()); // after size was computed
+
+ assertFalse(map.entrySet().iterator().hasNext());
+ assertFalse(map.keySet().iterator().hasNext());
+ assertFalse(map.containsKey("a"));
+ assertNull(map.get("a"));
+ }
+
+ public void testEmpty_NoPrefix2() {
+ RefMap map = new RefMap();
+ assertTrue(map.isEmpty()); // before size was computed
+ assertEquals(0, map.size());
+ assertTrue(map.isEmpty()); // after size was computed
+
+ assertFalse(map.entrySet().iterator().hasNext());
+ assertFalse(map.keySet().iterator().hasNext());
+ assertFalse(map.containsKey("a"));
+ assertNull(map.get("a"));
+ }
+
+ public void testNotEmpty_NoPrefix() {
+ final Ref master = newRef("refs/heads/master", ID_ONE);
+ packed = toList(master);
+
+ RefMap map = new RefMap("", packed, loose, resolved);
+ assertFalse(map.isEmpty()); // before size was computed
+ assertEquals(1, map.size());
+ assertFalse(map.isEmpty()); // after size was computed
+ assertSame(master, map.values().iterator().next());
+ }
+
+ public void testEmpty_WithPrefix() {
+ final Ref master = newRef("refs/heads/master", ID_ONE);
+ packed = toList(master);
+
+ RefMap map = new RefMap("refs/tags/", packed, loose, resolved);
+ assertTrue(map.isEmpty()); // before size was computed
+ assertEquals(0, map.size());
+ assertTrue(map.isEmpty()); // after size was computed
+
+ assertFalse(map.entrySet().iterator().hasNext());
+ assertFalse(map.keySet().iterator().hasNext());
+ }
+
+ public void testNotEmpty_WithPrefix() {
+ final Ref master = newRef("refs/heads/master", ID_ONE);
+ packed = toList(master);
+
+ RefMap map = new RefMap("refs/heads/", packed, loose, resolved);
+ assertFalse(map.isEmpty()); // before size was computed
+ assertEquals(1, map.size());
+ assertFalse(map.isEmpty()); // after size was computed
+ assertSame(master, map.values().iterator().next());
+ }
+
+ public void testClear() {
+ final Ref master = newRef("refs/heads/master", ID_ONE);
+ loose = toList(master);
+
+ RefMap map = new RefMap("", packed, loose, resolved);
+ assertSame(master, map.get("refs/heads/master"));
+
+ map.clear();
+ assertNull(map.get("refs/heads/master"));
+ assertTrue(map.isEmpty());
+ assertEquals(0, map.size());
+ }
+
+ public void testIterator_RefusesRemove() {
+ final Ref master = newRef("refs/heads/master", ID_ONE);
+ loose = toList(master);
+
+ RefMap map = new RefMap("", packed, loose, resolved);
+ Iterator<Ref> itr = map.values().iterator();
+ assertTrue(itr.hasNext());
+ assertSame(master, itr.next());
+ try {
+ itr.remove();
+ fail("iterator allowed remove");
+ } catch (UnsupportedOperationException err) {
+ // expected
+ }
+ }
+
+ public void testIterator_FailsAtEnd() {
+ final Ref master = newRef("refs/heads/master", ID_ONE);
+ loose = toList(master);
+
+ RefMap map = new RefMap("", packed, loose, resolved);
+ Iterator<Ref> itr = map.values().iterator();
+ assertTrue(itr.hasNext());
+ assertSame(master, itr.next());
+ try {
+ itr.next();
+ fail("iterator allowed next");
+ } catch (NoSuchElementException err) {
+ // expected
+ }
+ }
+
+ public void testIterator_MissingUnresolvedSymbolicRefIsBug() {
+ final Ref master = newRef("refs/heads/master", ID_ONE);
+ final Ref headR = newRef("HEAD", master);
+
+ loose = toList(master);
+ // loose should have added newRef("HEAD", "refs/heads/master")
+ resolved = toList(headR);
+
+ RefMap map = new RefMap("", packed, loose, resolved);
+ Iterator<Ref> itr = map.values().iterator();
+ try {
+ itr.hasNext();
+ fail("iterator did not catch bad input");
+ } catch (IllegalStateException err) {
+ // expected
+ }
+ }
+
+ public void testMerge_HeadMaster() {
+ final Ref master = newRef("refs/heads/master", ID_ONE);
+ final Ref headU = newRef("HEAD", "refs/heads/master");
+ final Ref headR = newRef("HEAD", master);
+
+ loose = toList(headU, master);
+ resolved = toList(headR);
+
+ RefMap map = new RefMap("", packed, loose, resolved);
+ assertEquals(2, map.size());
+ assertFalse(map.isEmpty());
+ assertTrue(map.containsKey("refs/heads/master"));
+ assertSame(master, map.get("refs/heads/master"));
+
+ // resolved overrides loose given same name
+ assertSame(headR, map.get("HEAD"));
+
+ Iterator<Ref> itr = map.values().iterator();
+ assertTrue(itr.hasNext());
+ assertSame(headR, itr.next());
+ assertTrue(itr.hasNext());
+ assertSame(master, itr.next());
+ assertFalse(itr.hasNext());
+ }
+
+ public void testMerge_PackedLooseLoose() {
+ final Ref refA = newRef("A", ID_ONE);
+ final Ref refB_ONE = newRef("B", ID_ONE);
+ final Ref refB_TWO = newRef("B", ID_TWO);
+ final Ref refc = newRef("c", ID_ONE);
+
+ packed = toList(refA, refB_ONE);
+ loose = toList(refB_TWO, refc);
+
+ RefMap map = new RefMap("", packed, loose, resolved);
+ assertEquals(3, map.size());
+ assertFalse(map.isEmpty());
+ assertTrue(map.containsKey(refA.getName()));
+ assertSame(refA, map.get(refA.getName()));
+
+ // loose overrides packed given same name
+ assertSame(refB_TWO, map.get(refB_ONE.getName()));
+
+ Iterator<Ref> itr = map.values().iterator();
+ assertTrue(itr.hasNext());
+ assertSame(refA, itr.next());
+ assertTrue(itr.hasNext());
+ assertSame(refB_TWO, itr.next());
+ assertTrue(itr.hasNext());
+ assertSame(refc, itr.next());
+ assertFalse(itr.hasNext());
+ }
+
+ public void testMerge_WithPrefix() {
+ final Ref a = newRef("refs/heads/A", ID_ONE);
+ final Ref b = newRef("refs/heads/foo/bar/B", ID_TWO);
+ final Ref c = newRef("refs/heads/foo/rab/C", ID_TWO);
+ final Ref g = newRef("refs/heads/g", ID_ONE);
+ packed = toList(a, b, c, g);
+
+ RefMap map = new RefMap("refs/heads/foo/", packed, loose, resolved);
+ assertEquals(2, map.size());
+
+ assertSame(b, map.get("bar/B"));
+ assertSame(c, map.get("rab/C"));
+ assertNull(map.get("refs/heads/foo/bar/B"));
+ assertNull(map.get("refs/heads/A"));
+
+ assertTrue(map.containsKey("bar/B"));
+ assertTrue(map.containsKey("rab/C"));
+ assertFalse(map.containsKey("refs/heads/foo/bar/B"));
+ assertFalse(map.containsKey("refs/heads/A"));
+
+ Iterator<Map.Entry<String, Ref>> itr = map.entrySet().iterator();
+ Map.Entry<String, Ref> ent;
+ assertTrue(itr.hasNext());
+ ent = itr.next();
+ assertEquals("bar/B", ent.getKey());
+ assertSame(b, ent.getValue());
+ assertTrue(itr.hasNext());
+ ent = itr.next();
+ assertEquals("rab/C", ent.getKey());
+ assertSame(c, ent.getValue());
+ assertFalse(itr.hasNext());
+ }
+
+ public void testPut_KeyMustMatchName_NoPrefix() {
+ final Ref refA = newRef("refs/heads/A", ID_ONE);
+ RefMap map = new RefMap("", packed, loose, resolved);
+ try {
+ map.put("FOO", refA);
+ fail("map accepted invalid key/value pair");
+ } catch (IllegalArgumentException err) {
+ // expected
+ }
+ }
+
+ public void testPut_KeyMustMatchName_WithPrefix() {
+ final Ref refA = newRef("refs/heads/A", ID_ONE);
+ RefMap map = new RefMap("refs/heads/", packed, loose, resolved);
+ try {
+ map.put("FOO", refA);
+ fail("map accepted invalid key/value pair");
+ } catch (IllegalArgumentException err) {
+ // expected
+ }
+ }
+
+ public void testPut_NoPrefix() {
+ final Ref refA_one = newRef("refs/heads/A", ID_ONE);
+ final Ref refA_two = newRef("refs/heads/A", ID_TWO);
+
+ packed = toList(refA_one);
+
+ RefMap map = new RefMap("", packed, loose, resolved);
+ assertSame(refA_one, map.get(refA_one.getName()));
+ assertSame(refA_one, map.put(refA_one.getName(), refA_two));
+
+ // map changed, but packed, loose did not
+ assertSame(refA_two, map.get(refA_one.getName()));
+ assertSame(refA_one, packed.get(0));
+ assertEquals(0, loose.size());
+
+ assertSame(refA_two, map.put(refA_one.getName(), refA_one));
+ assertSame(refA_one, map.get(refA_one.getName()));
+ }
+
+ public void testPut_WithPrefix() {
+ final Ref refA_one = newRef("refs/heads/A", ID_ONE);
+ final Ref refA_two = newRef("refs/heads/A", ID_TWO);
+
+ packed = toList(refA_one);
+
+ RefMap map = new RefMap("refs/heads/", packed, loose, resolved);
+ assertSame(refA_one, map.get("A"));
+ assertSame(refA_one, map.put("A", refA_two));
+
+ // map changed, but packed, loose did not
+ assertSame(refA_two, map.get("A"));
+ assertSame(refA_one, packed.get(0));
+ assertEquals(0, loose.size());
+
+ assertSame(refA_two, map.put("A", refA_one));
+ assertSame(refA_one, map.get("A"));
+ }
+
+ public void testPut_CollapseResolved() {
+ final Ref master = newRef("refs/heads/master", ID_ONE);
+ final Ref headU = newRef("HEAD", "refs/heads/master");
+ final Ref headR = newRef("HEAD", master);
+ final Ref a = newRef("refs/heads/A", ID_ONE);
+
+ loose = toList(headU, master);
+ resolved = toList(headR);
+
+ RefMap map = new RefMap("", packed, loose, resolved);
+ assertNull(map.put(a.getName(), a));
+ assertSame(a, map.get(a.getName()));
+ assertSame(headR, map.get("HEAD"));
+ }
+
+ public void testRemove() {
+ final Ref master = newRef("refs/heads/master", ID_ONE);
+ final Ref headU = newRef("HEAD", "refs/heads/master");
+ final Ref headR = newRef("HEAD", master);
+
+ packed = toList(master);
+ loose = toList(headU, master);
+ resolved = toList(headR);
+
+ RefMap map = new RefMap("", packed, loose, resolved);
+ assertNull(map.remove("not.a.reference"));
+
+ assertSame(master, map.remove("refs/heads/master"));
+ assertNull(map.get("refs/heads/master"));
+
+ assertSame(headR, map.remove("HEAD"));
+ assertNull(map.get("HEAD"));
+
+ assertTrue(map.isEmpty());
+ }
+
+ public void testToString_NoPrefix() {
+ final Ref a = newRef("refs/heads/A", ID_ONE);
+ final Ref b = newRef("refs/heads/B", ID_TWO);
+
+ packed = toList(a, b);
+
+ StringBuilder exp = new StringBuilder();
+ exp.append("[");
+ exp.append(a.toString());
+ exp.append(", ");
+ exp.append(b.toString());
+ exp.append("]");
+
+ RefMap map = new RefMap("", packed, loose, resolved);
+ assertEquals(exp.toString(), map.toString());
+ }
+
+ public void testToString_WithPrefix() {
+ final Ref a = newRef("refs/heads/A", ID_ONE);
+ final Ref b = newRef("refs/heads/foo/B", ID_TWO);
+ final Ref c = newRef("refs/heads/foo/C", ID_TWO);
+ final Ref g = newRef("refs/heads/g", ID_ONE);
+
+ packed = toList(a, b, c, g);
+
+ StringBuilder exp = new StringBuilder();
+ exp.append("[");
+ exp.append(b.toString());
+ exp.append(", ");
+ exp.append(c.toString());
+ exp.append("]");
+
+ RefMap map = new RefMap("refs/heads/foo/", packed, loose, resolved);
+ assertEquals(exp.toString(), map.toString());
+ }
+
+ public void testEntryType() {
+ final Ref a = newRef("refs/heads/A", ID_ONE);
+ final Ref b = newRef("refs/heads/B", ID_TWO);
+
+ packed = toList(a, b);
+
+ RefMap map = new RefMap("refs/heads/", packed, loose, resolved);
+ Iterator<Map.Entry<String, Ref>> itr = map.entrySet().iterator();
+ Map.Entry<String, Ref> ent_a = itr.next();
+ Map.Entry<String, Ref> ent_b = itr.next();
+
+ assertEquals(ent_a.hashCode(), "A".hashCode());
+ assertTrue(ent_a.equals(ent_a));
+ assertFalse(ent_a.equals(ent_b));
+
+ assertEquals(a.toString(), ent_a.toString());
+ }
+
+ public void testEntryTypeSet() {
+ final Ref refA_one = newRef("refs/heads/A", ID_ONE);
+ final Ref refA_two = newRef("refs/heads/A", ID_TWO);
+
+ packed = toList(refA_one);
+
+ RefMap map = new RefMap("refs/heads/", packed, loose, resolved);
+ assertSame(refA_one, map.get("A"));
+
+ Map.Entry<String, Ref> ent = map.entrySet().iterator().next();
+ assertEquals("A", ent.getKey());
+ assertSame(refA_one, ent.getValue());
+
+ assertSame(refA_one, ent.setValue(refA_two));
+ assertSame(refA_two, ent.getValue());
+ assertSame(refA_two, map.get("A"));
+ assertEquals(1, map.size());
+ }
+
+ private RefList<Ref> toList(Ref... refs) {
+ RefList.Builder<Ref> b = new RefList.Builder<Ref>(refs.length);
+ b.addAll(refs, 0, refs.length);
+ return b.toRefList();
+ }
+
+ private static Ref newRef(String name, String dst) {
+ return newRef(name,
+ new ObjectIdRef.Unpeeled(Ref.Storage.NEW, dst, null));
+ }
+
+ private static Ref newRef(String name, Ref dst) {
+ return new SymbolicRef(name, dst);
+ }
+
+ private static Ref newRef(String name, ObjectId id) {
+ return new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, id);
+ }
+}
diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java
index 007a0e8d46..4a5d4603ca 100644
--- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java
+++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2010, Google Inc.
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log.
@@ -146,7 +147,7 @@ final class AWTPlotRenderer extends AbstractPlotRenderer<SwingLane, Color> {
@Override
protected int drawLabel(int x, int y, Ref ref) {
String txt;
- String name = ref.getOrigName();
+ String name = ref.getName();
if (name.startsWith(Constants.R_HEADS)) {
g.setBackground(Color.GREEN);
txt = name.substring(Constants.R_HEADS.length());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java
index b6da244e83..bd773c4705 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java
@@ -49,6 +49,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.FileLock;
@@ -65,6 +66,15 @@ import java.nio.channels.OverlappingFileLockException;
* name.
*/
public class LockFile {
+ static final String SUFFIX = ".lock"; //$NON-NLS-1$
+
+ /** Filter to skip over active lock files when listing a directory. */
+ static final FilenameFilter FILTER = new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return !name.endsWith(SUFFIX);
+ }
+ };
+
private final File ref;
private final File lck;
@@ -87,7 +97,7 @@ public class LockFile {
*/
public LockFile(final File f) {
ref = f;
- lck = new File(ref.getParentFile(), ref.getName() + ".lock");
+ lck = new File(ref.getParentFile(), ref.getName() + SUFFIX);
}
/**
@@ -335,6 +345,30 @@ public class LockFile {
}
/**
+ * Wait until the lock file information differs from the old file.
+ * <p>
+ * This method tests both the length and the last modification date. If both
+ * are the same, this method sleeps until it can force the new lock file's
+ * modification date to be later than the target file.
+ *
+ * @throws InterruptedException
+ * the thread was interrupted before the last modified date of
+ * the lock file was different from the last modified date of
+ * the target file.
+ */
+ public void waitForStatChange() throws InterruptedException {
+ if (ref.length() == lck.length()) {
+ long otime = ref.lastModified();
+ long ntime = lck.lastModified();
+ while (otime == ntime) {
+ Thread.sleep(25 /* milliseconds */);
+ lck.setLastModified(System.currentTimeMillis());
+ ntime = lck.lastModified();
+ }
+ }
+ }
+
+ /**
* Commit this change and release the lock.
* <p>
* If this method fails (returns false) the lock is still released.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
new file mode 100644
index 0000000000..babfc6f075
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * 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 {@link Ref} that points directly at an {@link ObjectId}. */
+public abstract class ObjectIdRef implements Ref {
+ /** Any reference whose peeled value is not yet known. */
+ public static class Unpeeled extends ObjectIdRef {
+ /**
+ * Create a new ref pairing.
+ *
+ * @param st
+ * method used to store this ref.
+ * @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.
+ */
+ public Unpeeled(Storage st, String name, ObjectId id) {
+ super(st, name, id);
+ }
+
+ public ObjectId getPeeledObjectId() {
+ return null;
+ }
+
+ public boolean isPeeled() {
+ return false;
+ }
+ }
+
+ /** An annotated tag whose peeled object has been cached. */
+ public static class PeeledTag extends ObjectIdRef {
+ private final ObjectId peeledObjectId;
+
+ /**
+ * Create a new ref pairing.
+ *
+ * @param st
+ * method used to store this ref.
+ * @param name
+ * name of this ref.
+ * @param id
+ * current value of the ref.
+ * @param p
+ * the first non-tag object that tag {@code id} points to.
+ */
+ public PeeledTag(Storage st, String name, ObjectId id, ObjectId p) {
+ super(st, name, id);
+ peeledObjectId = p;
+ }
+
+ public ObjectId getPeeledObjectId() {
+ return peeledObjectId;
+ }
+
+ public boolean isPeeled() {
+ return true;
+ }
+ }
+
+ /** A reference to a non-tag object coming from a cached source. */
+ public static class PeeledNonTag extends ObjectIdRef {
+ /**
+ * Create a new ref pairing.
+ *
+ * @param st
+ * method used to store this ref.
+ * @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.
+ */
+ public PeeledNonTag(Storage st, String name, ObjectId id) {
+ super(st, name, id);
+ }
+
+ public ObjectId getPeeledObjectId() {
+ return null;
+ }
+
+ public boolean isPeeled() {
+ return true;
+ }
+ }
+
+ private final String name;
+
+ private final Storage storage;
+
+ private final ObjectId objectId;
+
+ /**
+ * Create a new ref pairing.
+ *
+ * @param st
+ * method used to store this ref.
+ * @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.
+ */
+ protected ObjectIdRef(Storage st, String name, ObjectId id) {
+ this.name = name;
+ this.storage = st;
+ this.objectId = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isSymbolic() {
+ return false;
+ }
+
+ public Ref getLeaf() {
+ return this;
+ }
+
+ public Ref getTarget() {
+ return this;
+ }
+
+ public ObjectId getObjectId() {
+ return objectId;
+ }
+
+ public Storage getStorage() {
+ return storage;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder r = new StringBuilder();
+ r.append("Ref[");
+ r.append(getName());
+ r.append('=');
+ r.append(ObjectId.toString(getObjectId()));
+ r.append(']');
+ return r.toString();
+ }
+}
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 162e399d99..f119c44fe2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
@@ -50,12 +50,12 @@ package org.eclipse.jgit.lib;
* identifier. The object identifier can be any valid Git object (blob, tree,
* commit, annotated tag, ...).
* <p>
- * The ref name has the attributes of the ref that was asked for as well as
- * the ref it was resolved to for symbolic refs plus the object id it points
- * to and (for tags) the peeled target object id, i.e. the tag resolved
- * recursively until a non-tag object is referenced.
+ * The ref name has the attributes of the ref that was asked for as well as the
+ * ref it was resolved to for symbolic refs plus the object id it points to and
+ * (for tags) the peeled target object id, i.e. the tag resolved recursively
+ * until a non-tag object is referenced.
*/
-public class Ref {
+public interface Ref {
/** Location where a {@link Ref} is stored. */
public static enum Storage {
/**
@@ -73,8 +73,7 @@ public class Ref {
LOOSE(true, false),
/**
- * The ref is stored in the <code>packed-refs</code> file, with
- * others.
+ * The ref is stored in the <code>packed-refs</code> file, with others.
* <p>
* Updating this ref requires rewriting the file, with perhaps many
* other refs being included at the same time.
@@ -122,123 +121,63 @@ public class Ref {
}
}
- private final Storage storage;
-
- private final String name;
-
- private ObjectId objectId;
-
- private ObjectId peeledObjectId;
-
- private final String origName;
-
- private final boolean peeled;
-
/**
- * Create a new ref pairing.
+ * What this ref is called within the repository.
*
- * @param st
- * method used to store this ref.
- * @param origName
- * The name used to resolve this ref
- * @param refName
- * name of this ref.
- * @param id
- * current value of the ref. May be null to indicate a ref that
- * does not exist yet.
+ * @return name of this ref.
*/
- public Ref(final Storage st, final String origName, final String refName, final ObjectId id) {
- this(st, origName, refName, id, null, false);
- }
+ public String getName();
/**
- * Create a new ref pairing.
+ * Test if this reference is a symbolic reference.
+ * <p>
+ * A symbolic reference does not have its own {@link ObjectId} value, but
+ * instead points to another {@code Ref} in the same database and always
+ * uses that other reference's value as its own.
*
- * @param st
- * method used to store this ref.
- * @param refName
- * name of this ref.
- * @param id
- * current value of the ref. May be null to indicate a ref that
- * does not exist yet.
+ * @return true if this is a symbolic reference; false if this reference
+ * contains its own ObjectId.
*/
- public Ref(final Storage st, final String refName, final ObjectId id) {
- this(st, refName, refName, id, null, false);
- }
+ public abstract boolean isSymbolic();
/**
- * Create a new ref pairing.
+ * Traverse target references until {@link #isSymbolic()} is false.
+ * <p>
+ * If {@link #isSymbolic()} is false, returns {@code this}.
+ * <p>
+ * If {@link #isSymbolic()} is true, this method recursively traverses
+ * {@link #getTarget()} until {@link #isSymbolic()} returns false.
+ * <p>
+ * This method is effectively
*
- * @param st
- * method used to store this ref.
- * @param origName
- * The name used to resolve this ref
- * @param refName
- * name of this ref.
- * @param id
- * current value of the ref. May be null to indicate a ref that
- * does not exist yet.
- * @param peel
- * peeled value of the ref's tag. May be null if this is not a
- * tag or not yet peeled (in which case the next parameter should be null)
- * @param peeled
- * true if peel represents a the peeled value of the object
- */
- public Ref(final Storage st, final String origName, final String refName, final ObjectId id,
- final ObjectId peel, final boolean peeled) {
- storage = st;
- this.origName = origName;
- name = refName;
- objectId = id;
- peeledObjectId = peel;
- this.peeled = peeled;
- }
-
- /**
- * Create a new ref pairing.
+ * <pre>
+ * return isSymbolic() ? getTarget().getLeaf() : this;
+ * </pre>
*
- * @param st
- * method used to store this ref.
- * @param refName
- * name of this ref.
- * @param id
- * current value of the ref. May be null to indicate a ref that
- * does not exist yet.
- * @param peel
- * peeled value of the ref's tag. May be null if this is not a
- * tag or the peeled value is not known.
- * @param peeled
- * true if peel represents a the peeled value of the object
+ * @return the reference that actually stores the ObjectId value.
*/
- public Ref(final Storage st, final String refName, final ObjectId id,
- final ObjectId peel, boolean peeled) {
- this(st, refName, refName, id, peel, peeled);
- }
+ public abstract Ref getLeaf();
/**
- * What this ref is called within the repository.
+ * Get the reference this reference points to, or {@code this}.
+ * <p>
+ * If {@link #isSymbolic()} is true this method returns the reference it
+ * directly names, which might not be the leaf reference, but could be
+ * another symbolic reference.
+ * <p>
+ * If this is a leaf level reference that contains its own ObjectId,this
+ * method returns {@code this}.
*
- * @return name of this ref.
- */
- public String getName() {
- return name;
- }
-
- /**
- * @return the originally resolved name
+ * @return the target reference, or {@code this}.
*/
- public String getOrigName() {
- return origName;
- }
+ public abstract Ref getTarget();
/**
* Cached value of this ref.
*
* @return the value of this ref at the last time we read it.
*/
- public ObjectId getObjectId() {
- return objectId;
- }
+ public abstract ObjectId getObjectId();
/**
* Cached value of <code>ref^{}</code> (the ref peeled to commit).
@@ -247,18 +186,12 @@ public class Ref {
* blob) that the annotated tag refers to; null if this ref does not
* refer to an annotated tag.
*/
- public ObjectId getPeeledObjectId() {
- if (!peeled)
- return null;
- return peeledObjectId;
- }
+ public abstract ObjectId getPeeledObjectId();
/**
* @return whether the Ref represents a peeled tag
*/
- public boolean isPeeled() {
- return peeled;
- }
+ public abstract boolean isPeeled();
/**
* How was this ref obtained?
@@ -268,18 +201,5 @@ public class Ref {
*
* @return type of ref.
*/
- public Storage getStorage() {
- return storage;
- }
-
- public String toString() {
- String o = "";
- if (!origName.equals(name))
- o = "(" + origName + ")";
- return "Ref[" + o + name + "=" + ObjectId.toString(getObjectId()) + "]";
- }
-
- void setPeeledObjectId(final ObjectId id) {
- peeledObjectId = id;
- }
+ public abstract Storage getStorage();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java
index fe6d210c11..f14488b73e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
- * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2010, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -61,12 +61,12 @@ public class RefComparator implements Comparator<Ref> {
public static final RefComparator INSTANCE = new RefComparator();
public int compare(final Ref o1, final Ref o2) {
- return o1.getOrigName().compareTo(o2.getOrigName());
+ return compareTo(o1, o2);
}
/**
* Sorts the collection of refs, returning a new collection.
- *
+ *
* @param refs
* collection to be sorted
* @return sorted collection of refs
@@ -76,4 +76,30 @@ public class RefComparator implements Comparator<Ref> {
Collections.sort(r, INSTANCE);
return r;
}
+
+ /**
+ * Compare a reference to a name.
+ *
+ * @param o1
+ * the reference instance.
+ * @param o2
+ * the name to compare to.
+ * @return standard Comparator result of < 0, 0, > 0.
+ */
+ public static int compareTo(Ref o1, String o2) {
+ return o1.getName().compareTo(o2);
+ }
+
+ /**
+ * Compare two references by name.
+ *
+ * @param o1
+ * the reference instance.
+ * @param o2
+ * the other reference instance.
+ * @return standard Comparator result of < 0, 0, > 0.
+ */
+ public static int compareTo(final Ref o1, final Ref o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
}
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 fb6a27db30..21e7041b32 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -1,6 +1,5 @@
/*
- * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2010, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -44,499 +43,156 @@
package org.eclipse.jgit.lib;
-import static org.eclipse.jgit.lib.Constants.R_TAGS;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.HashMap;
import java.util.Map;
-import org.eclipse.jgit.errors.ObjectWritingException;
-import org.eclipse.jgit.lib.Ref.Storage;
-import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.IO;
-import org.eclipse.jgit.util.RawParseUtils;
-
-class RefDatabase {
- private static final String REFS_SLASH = "refs/";
-
- private static final String[] refSearchPaths = { "", REFS_SLASH,
- R_TAGS, Constants.R_HEADS, Constants.R_REMOTES };
-
- private final Repository db;
-
- private final File gitDir;
-
- private final File refsDir;
-
- private Map<String, Ref> looseRefs;
- private Map<String, Long> looseRefsMTime;
- private Map<String, String> looseSymRefs;
-
- private final File packedRefsFile;
-
- private Map<String, Ref> packedRefs;
-
- private long packedRefsLastModified;
-
- private long packedRefsLength;
-
- int lastRefModification;
-
- int lastNotifiedRefModification;
-
- private int refModificationCounter;
-
- RefDatabase(final Repository r) {
- db = r;
- gitDir = db.getDirectory();
- refsDir = FS.resolve(gitDir, "refs");
- packedRefsFile = FS.resolve(gitDir, Constants.PACKED_REFS);
- clearCache();
- }
+/**
+ * Abstraction of name to {@link ObjectId} mapping.
+ * <p>
+ * A reference database stores a mapping of reference names to {@link ObjectId}.
+ * Every {@link Repository} has a single reference database, mapping names to
+ * the tips of the object graph contained by the {@link ObjectDatabase}.
+ */
+public abstract class RefDatabase {
+ /**
+ * Order of prefixes to search when using non-absolute references.
+ * <p>
+ * The implementation's {@link #getRef(String)} method must take this search
+ * space into consideration when locating a reference by name. The first
+ * entry in the path is always {@code ""}, ensuring that absolute references
+ * are resolved without further mangling.
+ */
+ protected static final String[] SEARCH_PATH = { "", //$NON-NLS-1$
+ Constants.R_REFS, //
+ Constants.R_TAGS, //
+ Constants.R_HEADS, //
+ Constants.R_REMOTES //
+ };
- synchronized void clearCache() {
- looseRefs = new HashMap<String, Ref>();
- looseRefsMTime = new HashMap<String, Long>();
- packedRefs = new HashMap<String, Ref>();
- looseSymRefs = new HashMap<String, String>();
- packedRefsLastModified = 0;
- packedRefsLength = 0;
- }
+ /**
+ * Maximum number of times a {@link SymbolicRef} can be traversed.
+ * <p>
+ * If the reference is nested deeper than this depth, the implementation
+ * should either fail, or at least claim the reference does not exist.
+ */
+ protected static final int MAX_SYMBOLIC_REF_DEPTH = 5;
- Repository getRepository() {
- return db;
- }
+ /** Magic value for {@link #getRefs(String)} to return all references. */
+ public static final String ALL = "";//$NON-NLS-1$
- void create() {
- refsDir.mkdir();
- new File(refsDir, "heads").mkdir();
- new File(refsDir, "tags").mkdir();
- }
+ /**
+ * Initialize a new reference database at this location.
+ *
+ * @throws IOException
+ * the database could not be created.
+ */
+ public abstract void create() throws IOException;
- ObjectId idOf(final String name) throws IOException {
- refreshPackedRefs();
- final Ref r = readRefBasic(name, 0);
- return r != null ? r.getObjectId() : null;
- }
+ /** Close any resources held by this database. */
+ public abstract void close();
/**
- * Create a command to update, create or delete a ref in this repository.
+ * Determine if a proposed reference name overlaps with an existing one.
+ * <p>
+ * Reference names use '/' as a component separator, and may be stored in a
+ * hierarchical storage such as a directory on the local filesystem.
+ * <p>
+ * If the reference "refs/heads/foo" exists then "refs/heads/foo/bar" must
+ * not exist, as a reference cannot have a value and also be a container for
+ * other references at the same time.
+ * <p>
+ * If the reference "refs/heads/foo/bar" exists than the reference
+ * "refs/heads/foo" cannot exist, for the same reason.
*
* @param name
- * name of the ref the caller wants to modify.
- * @return an update command. The caller must finish populating this command
- * and then invoke one of the update methods to actually make a
- * change.
+ * proposed name.
+ * @return true if the name overlaps with an existing reference; false if
+ * using this name right now would be safe.
* @throws IOException
- * a symbolic ref was passed in and could not be resolved back
- * to the base ref, as the symbolic ref could not be read.
+ * the database could not be read to check for conflicts.
*/
- RefUpdate newUpdate(final String name) throws IOException {
- return newUpdate(name, false);
- }
+ public abstract boolean isNameConflicting(String name) throws IOException;
/**
- * Create a command to update, create or delete a ref in this repository.
+ * Create a new update command to create, modify or delete a reference.
*
* @param name
- * name of the ref the caller wants to modify.
+ * the name of the reference.
* @param detach
- * true to detach the ref, i.e. replace symref with object ref
- * @return an update command. The caller must finish populating this command
- * and then invoke one of the update methods to actually make a
- * change.
+ * if {@code true} and {@code name} is currently a
+ * {@link SymbolicRef}, the update will replace it with an
+ * {@link ObjectIdRef}. Otherwise, the update will recursively
+ * traverse {@link SymbolicRef}s and operate on the leaf
+ * {@link ObjectIdRef}.
+ * @return a new update for the requested name; never null.
* @throws IOException
- * a symbolic ref was passed in and could not be resolved back
- * to the base ref, as the symbolic ref could not be read.
+ * the reference space cannot be accessed.
*/
- RefUpdate newUpdate(final String name, boolean detach) throws IOException {
- refreshPackedRefs();
- Ref r = readRefBasic(name, 0);
- if (r == null)
- r = new Ref(Ref.Storage.NEW, name, null);
- else if (detach)
- r = new Ref(Ref.Storage.NEW, name, r.getObjectId());
- return new RefUpdate(this, r, fileForRef(r.getName()));
- }
-
- void stored(final String origName, final String name, final ObjectId id, final long time) {
- synchronized (this) {
- looseRefs.put(name, new Ref(Ref.Storage.LOOSE, name, name, id));
- looseRefsMTime.put(name, time);
- setModified();
- }
- db.fireRefsMaybeChanged();
- }
+ public abstract RefUpdate newUpdate(String name, boolean detach)
+ throws IOException;
/**
- * An set of update operations for renaming a ref
+ * Create a new update command to rename a reference.
*
- * @param fromRef Old ref name
- * @param toRef New ref name
- * @return a RefUpdate operation to rename a ref
+ * @param fromName
+ * name of reference to rename from
+ * @param toName
+ * name of reference to rename to
+ * @return an update command that knows how to rename a branch to another.
* @throws IOException
+ * the reference space cannot be accessed.
*/
- RefRename newRename(String fromRef, String toRef) throws IOException {
- refreshPackedRefs();
- Ref f = readRefBasic(fromRef, 0);
- Ref t = new Ref(Ref.Storage.NEW, toRef, null);
- RefUpdate refUpdateFrom = new RefUpdate(this, f, fileForRef(f.getName()));
- RefUpdate refUpdateTo = new RefUpdate(this, t, fileForRef(t.getName()));
- return new RefRename(refUpdateTo, refUpdateFrom);
- }
+ public abstract RefRename newRename(String fromName, String toName)
+ throws IOException;
/**
- * Writes a symref (e.g. HEAD) to disk
+ * Read a single reference.
+ * <p>
+ * Aside from taking advantage of {@link #SEARCH_PATH}, this method may be
+ * able to more quickly resolve a single reference name than obtaining the
+ * complete namespace by {@code getRefs(ALL).get(name)}.
*
* @param name
- * symref name
- * @param target
- * pointed to ref
+ * the name of the reference. May be a short name which must be
+ * searched for using the standard {@link #SEARCH_PATH}.
+ * @return the reference (if it exists); else {@code null}.
* @throws IOException
+ * the reference space cannot be accessed.
*/
- void link(final String name, final String target) throws IOException {
- final byte[] content = Constants.encode("ref: " + target + "\n");
- lockAndWriteFile(fileForRef(name), content);
- synchronized (this) {
- looseSymRefs.remove(name);
- setModified();
- }
- db.fireRefsMaybeChanged();
- }
-
- void uncacheSymRef(String name) {
- synchronized(this) {
- looseSymRefs.remove(name);
- setModified();
- }
- }
-
- void uncacheRef(String name) {
- looseRefs.remove(name);
- looseRefsMTime.remove(name);
- packedRefs.remove(name);
- }
-
- private void setModified() {
- lastRefModification = refModificationCounter++;
- }
-
- Ref readRef(final String partialName) throws IOException {
- refreshPackedRefs();
- for (int k = 0; k < refSearchPaths.length; k++) {
- final Ref r = readRefBasic(refSearchPaths[k] + partialName, 0);
- if (r != null && r.getObjectId() != null)
- return r;
- }
- return null;
- }
+ public abstract Ref getRef(String name) throws IOException;
/**
- * @return all known refs (heads, tags, remotes).
+ * Get a section of the reference namespace.
+ *
+ * @param prefix
+ * prefix to search the namespace with; must end with {@code /}.
+ * If the empty string ({@link #ALL}), obtain a complete snapshot
+ * of all references.
+ * @return modifiable map that is a complete snapshot of the current
+ * reference namespace, with {@code prefix} removed from the start
+ * of each key. The map can be an unsorted map.
+ * @throws IOException
+ * the reference space cannot be accessed.
*/
- Map<String, Ref> getAllRefs() {
- return readRefs();
- }
+ public abstract Map<String, Ref> getRefs(String prefix) throws IOException;
/**
- * @return all tags; key is short tag name ("v1.0") and value of the entry
- * contains the ref with the full tag name ("refs/tags/v1.0").
+ * Peel a possibly unpeeled reference by traversing the annotated tags.
+ * <p>
+ * If the reference cannot be peeled (as it does not refer to an annotated
+ * tag) the peeled id stays null, but {@link Ref#isPeeled()} will be true.
+ * <p>
+ * Implementors should check {@link Ref#isPeeled()} before performing any
+ * additional work effort.
+ *
+ * @param ref
+ * The reference to peel
+ * @return {@code ref} if {@code ref.isPeeled()} is true; otherwise a new
+ * Ref object representing the same data as Ref, but isPeeled() will
+ * be true and getPeeledObjectId() will contain the peeled object
+ * (or null).
+ * @throws IOException
+ * the reference space or object space cannot be accessed.
*/
- Map<String, Ref> getTags() {
- final Map<String, Ref> tags = new HashMap<String, Ref>();
- for (final Ref r : readRefs().values()) {
- if (r.getName().startsWith(R_TAGS))
- tags.put(r.getName().substring(R_TAGS.length()), r);
- }
- return tags;
- }
-
- private Map<String, Ref> readRefs() {
- final HashMap<String, Ref> avail = new HashMap<String, Ref>();
- readPackedRefs(avail);
- readLooseRefs(avail, REFS_SLASH, refsDir);
- try {
- final Ref r = readRefBasic(Constants.HEAD, 0);
- if (r != null && r.getObjectId() != null)
- avail.put(Constants.HEAD, r);
- } catch (IOException e) {
- // ignore here
- }
- db.fireRefsMaybeChanged();
- return avail;
- }
-
- private synchronized void readPackedRefs(final Map<String, Ref> avail) {
- refreshPackedRefs();
- avail.putAll(packedRefs);
- }
-
- private void readLooseRefs(final Map<String, Ref> avail,
- final String prefix, final File dir) {
- final File[] entries = dir.listFiles();
- if (entries == null)
- return;
-
- for (final File ent : entries) {
- final String entName = ent.getName();
- if (".".equals(entName) || "..".equals(entName))
- continue;
- if (ent.isDirectory()) {
- readLooseRefs(avail, prefix + entName + "/", ent);
- } else {
- try {
- final Ref ref = readRefBasic(prefix + entName, 0);
- if (ref != null)
- avail.put(ref.getOrigName(), ref);
- } catch (IOException e) {
- continue;
- }
- }
- }
- }
-
- Ref peel(final Ref ref) {
- if (ref.isPeeled())
- return ref;
- ObjectId peeled = null;
- try {
- Object target = db.mapObject(ref.getObjectId(), ref.getName());
- while (target instanceof Tag) {
- final Tag tag = (Tag)target;
- peeled = tag.getObjId();
- if (Constants.TYPE_TAG.equals(tag.getType()))
- target = db.mapObject(tag.getObjId(), ref.getName());
- else
- break;
- }
- } catch (IOException e) {
- // Ignore a read error.  Callers will also get the same error
- // if they try to use the result of getPeeledObjectId.
- }
- return new Ref(ref.getStorage(), ref.getName(), ref.getObjectId(), peeled, true);
-
- }
-
- private File fileForRef(final String name) {
- if (name.startsWith(REFS_SLASH))
- return new File(refsDir, name.substring(REFS_SLASH.length()));
- return new File(gitDir, name);
- }
-
- private Ref readRefBasic(final String name, final int depth) throws IOException {
- return readRefBasic(name, name, depth);
- }
-
- private synchronized Ref readRefBasic(final String origName,
- final String name, final int depth) throws IOException {
- // Prefer loose ref to packed ref as the loose
- // file can be more up-to-date than a packed one.
- //
- Ref ref = looseRefs.get(origName);
- final File loose = fileForRef(name);
- final long mtime = loose.lastModified();
- if (ref != null) {
- Long cachedlastModified = looseRefsMTime.get(name);
- if (cachedlastModified != null && cachedlastModified == mtime) {
- if (packedRefs.containsKey(origName))
- return new Ref(Storage.LOOSE_PACKED, origName, ref
- .getObjectId(), ref.getPeeledObjectId(), ref
- .isPeeled());
- else
- return ref;
- }
- looseRefs.remove(origName);
- looseRefsMTime.remove(origName);
- }
-
- if (mtime == 0) {
- // If last modified is 0 the file does not exist.
- // Try packed cache.
- //
- ref = packedRefs.get(name);
- if (ref != null)
- if (!ref.getOrigName().equals(origName))
- ref = new Ref(Storage.LOOSE_PACKED, origName, name, ref.getObjectId());
- return ref;
- }
-
- String line = null;
- try {
- Long cachedlastModified = looseRefsMTime.get(name);
- if (cachedlastModified != null && cachedlastModified == mtime) {
- line = looseSymRefs.get(name);
- }
- if (line == null) {
- line = readLine(loose);
- looseRefsMTime.put(name, mtime);
- looseSymRefs.put(name, line);
- }
- } catch (FileNotFoundException notLoose) {
- return packedRefs.get(name);
- }
-
- if (line == null || line.length() == 0) {
- looseRefs.remove(origName);
- looseRefsMTime.remove(origName);
- return new Ref(Ref.Storage.LOOSE, origName, name, null);
- }
-
- if (line.startsWith("ref: ")) {
- if (depth >= 5) {
- throw new IOException("Exceeded maximum ref depth of " + depth
- + " at " + name + ". Circular reference?");
- }
-
- final String target = line.substring("ref: ".length());
- Ref r = readRefBasic(target, target, depth + 1);
- Long cachedMtime = looseRefsMTime.get(name);
- if (cachedMtime != null && cachedMtime != mtime)
- setModified();
- looseRefsMTime.put(name, mtime);
- if (r == null)
- return new Ref(Ref.Storage.LOOSE, origName, target, null);
- if (!origName.equals(r.getName()))
- r = new Ref(Ref.Storage.LOOSE_PACKED, origName, r.getName(), r.getObjectId(), r.getPeeledObjectId(), true);
- return r;
- }
-
- setModified();
-
- final ObjectId id;
- try {
- id = ObjectId.fromString(line);
- } catch (IllegalArgumentException notRef) {
- throw new IOException("Not a ref: " + name + ": " + line);
- }
-
- Storage storage;
- if (packedRefs.containsKey(name))
- storage = Ref.Storage.LOOSE_PACKED;
- else
- storage = Ref.Storage.LOOSE;
- ref = new Ref(storage, name, id);
- looseRefs.put(name, ref);
- looseRefsMTime.put(name, mtime);
-
- if (!origName.equals(name)) {
- ref = new Ref(Ref.Storage.LOOSE, origName, name, id);
- looseRefs.put(origName, ref);
- }
-
- return ref;
- }
-
- private synchronized void refreshPackedRefs() {
- final long currTime = packedRefsFile.lastModified();
- final long currLen = currTime == 0 ? 0 : packedRefsFile.length();
- if (currTime == packedRefsLastModified && currLen == packedRefsLength)
- return;
- if (currTime == 0) {
- packedRefsLastModified = 0;
- packedRefsLength = 0;
- packedRefs = new HashMap<String, Ref>();
- return;
- }
-
- final Map<String, Ref> newPackedRefs = new HashMap<String, Ref>();
- try {
- final BufferedReader b = openReader(packedRefsFile);
- try {
- String p;
- Ref last = null;
- while ((p = b.readLine()) != null) {
- if (p.charAt(0) == '#')
- continue;
-
- if (p.charAt(0) == '^') {
- if (last == null)
- throw new IOException("Peeled line before ref.");
-
- final ObjectId id = ObjectId.fromString(p.substring(1));
- last = new Ref(Ref.Storage.PACKED, last.getName(), last
- .getName(), last.getObjectId(), id, true);
- newPackedRefs.put(last.getName(), last);
- continue;
- }
-
- final int sp = p.indexOf(' ');
- final ObjectId id = ObjectId.fromString(p.substring(0, sp));
- final String name = copy(p, sp + 1, p.length());
- last = new Ref(Ref.Storage.PACKED, name, name, id);
- newPackedRefs.put(last.getName(), last);
- }
- } finally {
- b.close();
- }
- packedRefsLastModified = currTime;
- packedRefsLength = currLen;
- packedRefs = newPackedRefs;
- setModified();
- } catch (FileNotFoundException noPackedRefs) {
- // Ignore it and leave the new map empty.
- //
- packedRefsLastModified = 0;
- packedRefsLength = 0;
- packedRefs = newPackedRefs;
- } catch (IOException e) {
- throw new RuntimeException("Cannot read packed refs", e);
- }
- }
-
- private static String copy(final String src, final int off, final int end) {
- return new StringBuilder(end - off).append(src, off, end).toString();
- }
-
- private void lockAndWriteFile(File file, byte[] content) throws IOException {
- String name = file.getName();
- final LockFile lck = new LockFile(file);
- if (!lck.lock())
- throw new ObjectWritingException("Unable to lock " + name);
- try {
- lck.write(content);
- } catch (IOException ioe) {
- throw new ObjectWritingException("Unable to write " + name, ioe);
- }
- if (!lck.commit())
- throw new ObjectWritingException("Unable to write " + name);
- }
-
- synchronized void removePackedRef(String name) throws IOException {
- packedRefs.remove(name);
- writePackedRefs();
- }
-
- private void writePackedRefs() throws IOException {
- new RefWriter(packedRefs.values()) {
- @Override
- protected void writeFile(String name, byte[] content) throws IOException {
- lockAndWriteFile(new File(db.getDirectory(), name), content);
- }
- }.writePackedRefs();
- }
-
- private static String readLine(final File file)
- throws FileNotFoundException, IOException {
- final byte[] buf = IO.readFully(file, 4096);
- int n = buf.length;
-
- // remove trailing whitespaces
- while (n > 0 && Character.isWhitespace(buf[n - 1]))
- n--;
-
- if (n == 0)
- return null;
- return RawParseUtils.decode(buf, 0, n);
- }
-
- private static BufferedReader openReader(final File fileLocation)
- throws FileNotFoundException {
- return new BufferedReader(new InputStreamReader(new FileInputStream(
- fileLocation), Constants.CHARSET));
- }
+ public abstract Ref peel(Ref ref) throws IOException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java
new file mode 100644
index 0000000000..90ac0bf47e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java
@@ -0,0 +1,1006 @@
+/*
+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2009-2010, Google Inc.
+ * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
+ * 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.eclipse.jgit.lib.Constants.CHARSET;
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.LOGS;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
+import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
+import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import static org.eclipse.jgit.lib.Constants.R_REFS;
+import static org.eclipse.jgit.lib.Constants.R_REMOTES;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
+import static org.eclipse.jgit.lib.Constants.encode;
+import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jgit.errors.ObjectWritingException;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.RefList;
+import org.eclipse.jgit.util.RefMap;
+
+/**
+ * Traditional file system based {@link RefDatabase}.
+ * <p>
+ * This is the classical reference database representation for a Git repository.
+ * References are stored in two formats: loose, and packed.
+ * <p>
+ * Loose references are stored as individual files within the {@code refs/}
+ * directory. The file name matches the reference name and the file contents is
+ * the current {@link ObjectId} in string form.
+ * <p>
+ * Packed references are stored in a single text file named {@code packed-refs}.
+ * In the packed format, each reference is stored on its own line. This file
+ * reduces the number of files needed for large reference spaces, reducing the
+ * overall size of a Git repository on disk.
+ */
+public class RefDirectory extends RefDatabase {
+ /** Magic string denoting the start of a symbolic reference file. */
+ public static final String SYMREF = "ref: "; //$NON-NLS-1$
+
+ /** Magic string denoting the header of a packed-refs file. */
+ public static final String PACKED_REFS_HEADER = "# pack-refs with:"; //$NON-NLS-1$
+
+ /** If in the header, denotes the file has peeled data. */
+ public static final String PACKED_REFS_PEELED = " peeled"; //$NON-NLS-1$
+
+ private final Repository parent;
+
+ private final File gitDir;
+
+ private final File refsDir;
+
+ private final File logsDir;
+
+ private final File logsRefsDir;
+
+ private final File packedRefsFile;
+
+ /**
+ * Immutable sorted list of loose references.
+ * <p>
+ * Symbolic references in this collection are stored unresolved, that is
+ * their target appears to be a new reference with no ObjectId. These are
+ * converted into resolved references during a get operation, ensuring the
+ * live value is always returned.
+ */
+ private final AtomicReference<RefList<LooseRef>> looseRefs = new AtomicReference<RefList<LooseRef>>();
+
+ /** Immutable sorted list of packed references. */
+ private final AtomicReference<PackedRefList> packedRefs = new AtomicReference<PackedRefList>();
+
+ /**
+ * Number of modifications made to this database.
+ * <p>
+ * This counter is incremented when a change is made, or detected from the
+ * filesystem during a read operation.
+ */
+ private final AtomicInteger modCnt = new AtomicInteger();
+
+ /**
+ * Last {@link #modCnt} that we sent to listeners.
+ * <p>
+ * This value is compared to {@link #modCnt}, and a notification is sent to
+ * the listeners only when it differs.
+ */
+ private final AtomicInteger lastNotifiedModCnt = new AtomicInteger();
+
+ RefDirectory(final Repository db) {
+ parent = db;
+ gitDir = db.getDirectory();
+ refsDir = FS.resolve(gitDir, R_REFS);
+ logsDir = FS.resolve(gitDir, LOGS);
+ logsRefsDir = FS.resolve(gitDir, LOGS + '/' + R_REFS);
+ packedRefsFile = FS.resolve(gitDir, PACKED_REFS);
+
+ looseRefs.set(RefList.<LooseRef> emptyList());
+ packedRefs.set(PackedRefList.NO_PACKED_REFS);
+ }
+
+ Repository getRepository() {
+ return parent;
+ }
+
+ public void create() throws IOException {
+ refsDir.mkdir();
+ logsDir.mkdir();
+ logsRefsDir.mkdir();
+
+ new File(refsDir, R_HEADS.substring(R_REFS.length())).mkdir();
+ new File(refsDir, R_TAGS.substring(R_REFS.length())).mkdir();
+ new File(logsRefsDir, R_HEADS.substring(R_REFS.length())).mkdir();
+ }
+
+ @Override
+ public void close() {
+ // We have no resources to close.
+ }
+
+ void rescan() {
+ looseRefs.set(RefList.<LooseRef> emptyList());
+ packedRefs.set(PackedRefList.NO_PACKED_REFS);
+ }
+
+ @Override
+ public boolean isNameConflicting(String name) throws IOException {
+ RefList<Ref> packed = getPackedRefs();
+ RefList<LooseRef> loose = getLooseRefs();
+
+ // Cannot be nested within an existing reference.
+ int lastSlash = name.lastIndexOf('/');
+ while (0 < lastSlash) {
+ String needle = name.substring(0, lastSlash);
+ if (loose.contains(needle) || packed.contains(needle))
+ return true;
+ lastSlash = name.lastIndexOf('/', lastSlash - 1);
+ }
+
+ // Cannot be the container of an existing reference.
+ String prefix = name + '/';
+ int idx;
+
+ idx = -(packed.find(prefix) + 1);
+ if (idx < packed.size() && packed.get(idx).getName().startsWith(prefix))
+ return true;
+
+ idx = -(loose.find(prefix) + 1);
+ if (idx < loose.size() && loose.get(idx).getName().startsWith(prefix))
+ return true;
+
+ return false;
+ }
+
+ private RefList<LooseRef> getLooseRefs() {
+ final RefList<LooseRef> oldLoose = looseRefs.get();
+
+ LooseScanner scan = new LooseScanner(oldLoose);
+ scan.scan(ALL);
+
+ RefList<LooseRef> loose;
+ if (scan.newLoose != null) {
+ loose = scan.newLoose.toRefList();
+ if (looseRefs.compareAndSet(oldLoose, loose))
+ modCnt.incrementAndGet();
+ } else
+ loose = oldLoose;
+ return loose;
+ }
+
+ @Override
+ public Ref getRef(final String needle) throws IOException {
+ final RefList<Ref> packed = getPackedRefs();
+ Ref ref = null;
+ for (String prefix : SEARCH_PATH) {
+ ref = readRef(prefix + needle, packed);
+ if (ref != null) {
+ ref = resolve(ref, 0, null, null, packed);
+ break;
+ }
+ }
+ fireRefsChanged();
+ return ref;
+ }
+
+ @Override
+ public Map<String, Ref> getRefs(String prefix) throws IOException {
+ final RefList<Ref> packed = getPackedRefs();
+ final RefList<LooseRef> oldLoose = looseRefs.get();
+
+ LooseScanner scan = new LooseScanner(oldLoose);
+ scan.scan(prefix);
+
+ RefList<LooseRef> loose;
+ if (scan.newLoose != null) {
+ loose = scan.newLoose.toRefList();
+ if (looseRefs.compareAndSet(oldLoose, loose))
+ modCnt.incrementAndGet();
+ } else
+ loose = oldLoose;
+ fireRefsChanged();
+
+ RefList.Builder<Ref> symbolic = scan.symbolic;
+ for (int idx = 0; idx < symbolic.size();) {
+ Ref ref = symbolic.get(idx);
+ ref = resolve(ref, 0, prefix, loose, packed);
+ if (ref != null && ref.getObjectId() != null) {
+ symbolic.set(idx, ref);
+ idx++;
+ } else {
+ // A broken symbolic reference, we have to drop it from the
+ // collections the client is about to receive. Should be a
+ // rare occurrence so pay a copy penalty.
+ loose = loose.remove(idx);
+ symbolic.remove(idx);
+ }
+ }
+
+ return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList());
+ }
+
+ @SuppressWarnings("unchecked")
+ private RefList<Ref> upcast(RefList<? extends Ref> loose) {
+ return (RefList<Ref>) loose;
+ }
+
+ private class LooseScanner {
+ private final RefList<LooseRef> curLoose;
+
+ private int curIdx;
+
+ final RefList.Builder<Ref> symbolic = new RefList.Builder<Ref>(4);
+
+ RefList.Builder<LooseRef> newLoose;
+
+ LooseScanner(final RefList<LooseRef> curLoose) {
+ this.curLoose = curLoose;
+ }
+
+ void scan(String prefix) {
+ if (ALL.equals(prefix)) {
+ scanOne(HEAD);
+ scanTree(R_REFS, refsDir);
+
+ // If any entries remain, they are deleted, drop them.
+ if (newLoose == null && curIdx < curLoose.size())
+ newLoose = curLoose.copy(curIdx);
+
+ } else if (prefix.startsWith(R_REFS) && prefix.endsWith("/")) {
+ curIdx = -(curLoose.find(prefix) + 1);
+ File dir = new File(refsDir, prefix.substring(R_REFS.length()));
+ scanTree(prefix, dir);
+
+ // Skip over entries still within the prefix; these have
+ // been removed from the directory.
+ while (curIdx < curLoose.size()) {
+ if (!curLoose.get(curIdx).getName().startsWith(prefix))
+ break;
+ if (newLoose == null)
+ newLoose = curLoose.copy(curIdx);
+ curIdx++;
+ }
+
+ // Keep any entries outside of the prefix space, we
+ // do not know anything about their status.
+ if (newLoose != null) {
+ while (curIdx < curLoose.size())
+ newLoose.add(curLoose.get(curIdx++));
+ }
+ }
+ }
+
+ private void scanTree(String prefix, File dir) {
+ final String[] entries = dir.list(LockFile.FILTER);
+ if (entries != null && 0 < entries.length) {
+ Arrays.sort(entries);
+ for (String name : entries) {
+ File e = new File(dir, name);
+ if (e.isDirectory())
+ scanTree(prefix + name + '/', e);
+ else
+ scanOne(prefix + name);
+ }
+ }
+ }
+
+ private void scanOne(String name) {
+ LooseRef cur;
+
+ if (curIdx < curLoose.size()) {
+ do {
+ cur = curLoose.get(curIdx);
+ int cmp = RefComparator.compareTo(cur, name);
+ if (cmp < 0) {
+ // Reference is not loose anymore, its been deleted.
+ // Skip the name in the new result list.
+ if (newLoose == null)
+ newLoose = curLoose.copy(curIdx);
+ curIdx++;
+ cur = null;
+ continue;
+ }
+
+ if (cmp > 0) // Newly discovered loose reference.
+ cur = null;
+ break;
+ } while (curIdx < curLoose.size());
+ } else
+ cur = null; // Newly discovered loose reference.
+
+ LooseRef n;
+ try {
+ n = scanRef(cur, name);
+ } catch (IOException notValid) {
+ n = null;
+ }
+
+ if (n != null) {
+ if (cur != n && newLoose == null)
+ newLoose = curLoose.copy(curIdx);
+ if (newLoose != null)
+ newLoose.add(n);
+ if (n.isSymbolic())
+ symbolic.add(n);
+ } else if (cur != null) {
+ // Tragically, this file is no longer a loose reference.
+ // Kill our cached entry of it.
+ if (newLoose == null)
+ newLoose = curLoose.copy(curIdx);
+ }
+
+ if (cur != null)
+ curIdx++;
+ }
+ }
+
+ @Override
+ public Ref peel(final Ref ref) throws IOException {
+ final Ref leaf = ref.getLeaf();
+ if (leaf.isPeeled() || leaf.getObjectId() == null)
+ return ref;
+
+ RevWalk rw = new RevWalk(getRepository());
+ RevObject obj = rw.parseAny(leaf.getObjectId());
+ ObjectIdRef newLeaf;
+ if (obj instanceof RevTag) {
+ do {
+ obj = rw.parseAny(((RevTag) obj).getObject());
+ } while (obj instanceof RevTag);
+
+ newLeaf = new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf
+ .getName(), leaf.getObjectId(), obj.copy());
+ } else {
+ newLeaf = new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf
+ .getName(), leaf.getObjectId());
+ }
+
+ // Try to remember this peeling in the cache, so we don't have to do
+ // it again in the future, but only if the reference is unchanged.
+ if (leaf.getStorage().isLoose()) {
+ RefList<LooseRef> curList = looseRefs.get();
+ int idx = curList.find(leaf.getName());
+ if (0 <= idx && curList.get(idx) == leaf) {
+ LooseRef asPeeled = ((LooseRef) leaf).peel(newLeaf);
+ RefList<LooseRef> newList = curList.set(idx, asPeeled);
+ looseRefs.compareAndSet(curList, newList);
+ }
+ }
+
+ return recreate(ref, newLeaf);
+ }
+
+ private static Ref recreate(final Ref old, final ObjectIdRef leaf) {
+ if (old.isSymbolic()) {
+ Ref dst = recreate(old.getTarget(), leaf);
+ return new SymbolicRef(old.getName(), dst);
+ }
+ return leaf;
+ }
+
+ void storedSymbolicRef(RefDirectoryUpdate u, long modified, String target) {
+ putLooseRef(newSymbolicRef(modified, u.getRef().getName(), target));
+ fireRefsChanged();
+ }
+
+ public RefDirectoryUpdate newUpdate(String name, boolean detach)
+ throws IOException {
+ final RefList<Ref> packed = getPackedRefs();
+ Ref ref = readRef(name, packed);
+ if (ref != null)
+ ref = resolve(ref, 0, null, null, packed);
+ if (ref == null)
+ ref = new ObjectIdRef.Unpeeled(NEW, name, null);
+ else if (detach && ref.isSymbolic())
+ ref = new ObjectIdRef.Unpeeled(LOOSE, name, ref.getObjectId());
+ return new RefDirectoryUpdate(this, ref);
+ }
+
+ @Override
+ public RefDirectoryRename newRename(String fromName, String toName)
+ throws IOException {
+ RefDirectoryUpdate from = newUpdate(fromName, false);
+ RefDirectoryUpdate to = newUpdate(toName, false);
+ return new RefDirectoryRename(from, to);
+ }
+
+ void stored(RefDirectoryUpdate update, long modified) {
+ final ObjectId target = update.getNewObjectId().copy();
+ final Ref leaf = update.getRef().getLeaf();
+ putLooseRef(new LooseUnpeeled(modified, leaf.getName(), target));
+ }
+
+ private void putLooseRef(LooseRef ref) {
+ RefList<LooseRef> cList, nList;
+ do {
+ cList = looseRefs.get();
+ nList = cList.put(ref);
+ } while (!looseRefs.compareAndSet(cList, nList));
+ modCnt.incrementAndGet();
+ fireRefsChanged();
+ }
+
+ void delete(RefDirectoryUpdate update) throws IOException {
+ Ref dst = update.getRef().getLeaf();
+ String name = dst.getName();
+
+ // Write the packed-refs file using an atomic update. We might
+ // wind up reading it twice, before and after the lock, to ensure
+ // we don't miss an edit made externally.
+ final PackedRefList packed = getPackedRefs();
+ if (packed.contains(name)) {
+ LockFile lck = new LockFile(packedRefsFile);
+ if (!lck.lock())
+ throw new IOException("Cannot lock " + packedRefsFile);
+ try {
+ PackedRefList cur = readPackedRefs(0, 0);
+ int idx = cur.find(name);
+ if (0 <= idx)
+ commitPackedRefs(lck, cur.remove(idx), packed);
+ } finally {
+ lck.unlock();
+ }
+ }
+
+ RefList<LooseRef> curLoose, newLoose;
+ do {
+ curLoose = looseRefs.get();
+ int idx = curLoose.find(name);
+ if (idx < 0)
+ break;
+ newLoose = curLoose.remove(idx);
+ } while (!looseRefs.compareAndSet(curLoose, newLoose));
+
+ int levels = levelsIn(name) - 2;
+ delete(logFor(name), levels);
+ if (dst.getStorage().isLoose()) {
+ update.unlock();
+ delete(fileFor(name), levels);
+ }
+
+ modCnt.incrementAndGet();
+ fireRefsChanged();
+ }
+
+ void log(final RefUpdate update, final String msg, final boolean deref)
+ throws IOException {
+ final ObjectId oldId = update.getOldObjectId();
+ final ObjectId newId = update.getNewObjectId();
+ final Ref ref = update.getRef();
+
+ PersonIdent ident = update.getRefLogIdent();
+ if (ident == null)
+ ident = new PersonIdent(parent);
+ else
+ ident = new PersonIdent(ident);
+
+ final StringBuilder r = new StringBuilder();
+ r.append(ObjectId.toString(oldId));
+ r.append(' ');
+ r.append(ObjectId.toString(newId));
+ r.append(' ');
+ r.append(ident.toExternalString());
+ r.append('\t');
+ r.append(msg);
+ r.append('\n');
+ final byte[] rec = encode(r.toString());
+
+ if (deref && ref.isSymbolic()) {
+ log(ref.getName(), rec);
+ log(ref.getLeaf().getName(), rec);
+ } else {
+ log(ref.getName(), rec);
+ }
+ }
+
+ private void log(final String refName, final byte[] rec) throws IOException {
+ final File log = logFor(refName);
+ final boolean write;
+ if (isLogAllRefUpdates() && shouldAutoCreateLog(refName))
+ write = true;
+ else if (log.isFile())
+ write = true;
+ else
+ write = false;
+
+ if (write) {
+ FileOutputStream out;
+ try {
+ out = new FileOutputStream(log, true);
+ } catch (FileNotFoundException err) {
+ final File dir = log.getParentFile();
+ if (dir.exists())
+ throw err;
+ if (!dir.mkdirs() && !dir.isDirectory())
+ throw new IOException("Cannot create directory " + dir);
+ out = new FileOutputStream(log, true);
+ }
+ try {
+ out.write(rec);
+ } finally {
+ out.close();
+ }
+ }
+ }
+
+ private boolean isLogAllRefUpdates() {
+ return parent.getConfig().getCore().isLogAllRefUpdates();
+ }
+
+ private boolean shouldAutoCreateLog(final String refName) {
+ return refName.equals(HEAD) //
+ || refName.startsWith(R_HEADS) //
+ || refName.startsWith(R_REMOTES);
+ }
+
+ private Ref resolve(final Ref ref, int depth, String prefix,
+ RefList<LooseRef> loose, RefList<Ref> packed) throws IOException {
+ if (ref.isSymbolic()) {
+ Ref dst = ref.getTarget();
+
+ if (MAX_SYMBOLIC_REF_DEPTH <= depth)
+ return null; // claim it doesn't exist
+
+ // If the cached value can be assumed to be current due to a
+ // recent scan of the loose directory, use it.
+ if (loose != null && dst.getName().startsWith(prefix)) {
+ int idx;
+ if (0 <= (idx = loose.find(dst.getName())))
+ dst = loose.get(idx);
+ else if (0 <= (idx = packed.find(dst.getName())))
+ dst = packed.get(idx);
+ else
+ return ref;
+ } else {
+ dst = readRef(dst.getName(), packed);
+ if (dst == null)
+ return ref;
+ }
+
+ dst = resolve(dst, depth + 1, prefix, loose, packed);
+ if (dst == null)
+ return null;
+ return new SymbolicRef(ref.getName(), dst);
+ }
+ return ref;
+ }
+
+ private PackedRefList getPackedRefs() throws IOException {
+ long size = packedRefsFile.length();
+ long mtime = size != 0 ? packedRefsFile.lastModified() : 0;
+
+ final PackedRefList curList = packedRefs.get();
+ if (size == curList.lastSize && mtime == curList.lastModified)
+ return curList;
+
+ final PackedRefList newList = readPackedRefs(size, mtime);
+ if (packedRefs.compareAndSet(curList, newList))
+ modCnt.incrementAndGet();
+ return newList;
+ }
+
+ private PackedRefList readPackedRefs(long size, long mtime)
+ throws IOException {
+ final BufferedReader br;
+ try {
+ br = new BufferedReader(new InputStreamReader(new FileInputStream(
+ packedRefsFile), CHARSET));
+ } catch (FileNotFoundException noPackedRefs) {
+ // Ignore it and leave the new list empty.
+ return PackedRefList.NO_PACKED_REFS;
+ }
+ try {
+ return new PackedRefList(parsePackedRefs(br), size, mtime);
+ } finally {
+ br.close();
+ }
+ }
+
+ private RefList<Ref> parsePackedRefs(final BufferedReader br)
+ throws IOException {
+ RefList.Builder<Ref> all = new RefList.Builder<Ref>();
+ Ref last = null;
+ boolean peeled = false;
+ boolean needSort = false;
+
+ String p;
+ while ((p = br.readLine()) != null) {
+ if (p.charAt(0) == '#') {
+ if (p.startsWith(PACKED_REFS_HEADER)) {
+ p = p.substring(PACKED_REFS_HEADER.length());
+ peeled = p.contains(PACKED_REFS_PEELED);
+ }
+ continue;
+ }
+
+ if (p.charAt(0) == '^') {
+ if (last == null)
+ throw new IOException("Peeled line before ref.");
+
+ ObjectId id = ObjectId.fromString(p.substring(1));
+ last = new ObjectIdRef.PeeledTag(PACKED, last.getName(), last
+ .getObjectId(), id);
+ all.set(all.size() - 1, last);
+ continue;
+ }
+
+ int sp = p.indexOf(' ');
+ ObjectId id = ObjectId.fromString(p.substring(0, sp));
+ String name = copy(p, sp + 1, p.length());
+ ObjectIdRef cur;
+ if (peeled)
+ cur = new ObjectIdRef.PeeledNonTag(PACKED, name, id);
+ else
+ cur = new ObjectIdRef.Unpeeled(PACKED, name, id);
+ if (last != null && RefComparator.compareTo(last, cur) > 0)
+ needSort = true;
+ all.add(cur);
+ last = cur;
+ }
+
+ if (needSort)
+ all.sort();
+ return all.toRefList();
+ }
+
+ private static String copy(final String src, final int off, final int end) {
+ // Don't use substring since it could leave a reference to the much
+ // larger existing string. Force construction of a full new object.
+ return new StringBuilder(end - off).append(src, off, end).toString();
+ }
+
+ private void commitPackedRefs(final LockFile lck, final RefList<Ref> refs,
+ final PackedRefList oldPackedList) throws IOException {
+ new RefWriter(refs) {
+ @Override
+ protected void writeFile(String name, byte[] content)
+ throws IOException {
+ lck.setNeedStatInformation(true);
+ try {
+ lck.write(content);
+ } catch (IOException ioe) {
+ throw new ObjectWritingException("Unable to write " + name,
+ ioe);
+ }
+ try {
+ lck.waitForStatChange();
+ } catch (InterruptedException e) {
+ lck.unlock();
+ throw new ObjectWritingException("Interrupted writing "
+ + name);
+ }
+ if (!lck.commit())
+ throw new ObjectWritingException("Unable to write " + name);
+
+ packedRefs.compareAndSet(oldPackedList, new PackedRefList(refs,
+ content.length, lck.getCommitLastModified()));
+ }
+ }.writePackedRefs();
+ }
+
+ private Ref readRef(String name, RefList<Ref> packed) throws IOException {
+ final RefList<LooseRef> curList = looseRefs.get();
+ final int idx = curList.find(name);
+ if (0 <= idx) {
+ final LooseRef o = curList.get(idx);
+ final LooseRef n = scanRef(o, name);
+ if (n == null) {
+ if (looseRefs.compareAndSet(curList, curList.remove(idx)))
+ modCnt.incrementAndGet();
+ return packed.get(name);
+ }
+
+ if (o == n)
+ return n;
+ if (looseRefs.compareAndSet(curList, curList.set(idx, n)))
+ modCnt.incrementAndGet();
+ return n;
+ }
+
+ final LooseRef n = scanRef(null, name);
+ if (n == null)
+ return packed.get(name);
+ if (looseRefs.compareAndSet(curList, curList.add(idx, n)))
+ modCnt.incrementAndGet();
+ return n;
+ }
+
+ private LooseRef scanRef(LooseRef ref, String name) throws IOException {
+ final File path = fileFor(name);
+ final long modified = path.lastModified();
+
+ if (ref != null) {
+ if (ref.getLastModified() == modified)
+ return ref;
+ name = ref.getName();
+ } else if (modified == 0)
+ return null;
+
+ final byte[] buf;
+ try {
+ buf = IO.readFully(path, 4096);
+ } catch (FileNotFoundException noFile) {
+ return null; // doesn't exist; not a reference.
+ }
+
+ int n = buf.length;
+ if (n == 0)
+ return null; // empty file; not a reference.
+
+ if (isSymRef(buf, n)) {
+ // trim trailing whitespace
+ while (0 < n && Character.isWhitespace(buf[n - 1]))
+ n--;
+ if (n < 6) {
+ String content = RawParseUtils.decode(buf, 0, n);
+ throw new IOException("Not a ref: " + name + ": " + content);
+ }
+ final String target = RawParseUtils.decode(buf, 5, n);
+ return newSymbolicRef(modified, name, target);
+ }
+
+ if (n < OBJECT_ID_STRING_LENGTH)
+ return null; // impossibly short object identifier; not a reference.
+
+ final ObjectId id;
+ try {
+ id = ObjectId.fromString(buf, 0);
+ } catch (IllegalArgumentException notRef) {
+ while (0 < n && Character.isWhitespace(buf[n - 1]))
+ n--;
+ String content = RawParseUtils.decode(buf, 0, n);
+ throw new IOException("Not a ref: " + name + ": " + content);
+ }
+ return new LooseUnpeeled(modified, name, id);
+ }
+
+ private static boolean isSymRef(final byte[] buf, int n) {
+ if (n < 6)
+ return false;
+ return /**/buf[0] == 'r' //
+ && buf[1] == 'e' //
+ && buf[2] == 'f' //
+ && buf[3] == ':' //
+ && buf[4] == ' ';
+ }
+
+ /** If the parent should fire listeners, fires them. */
+ private void fireRefsChanged() {
+ final int last = lastNotifiedModCnt.get();
+ final int curr = modCnt.get();
+ if (last != curr && lastNotifiedModCnt.compareAndSet(last, curr))
+ parent.fireRefsChanged();
+ }
+
+ /**
+ * Create a reference update to write a temporary reference.
+ *
+ * @return an update for a new temporary reference.
+ * @throws IOException
+ * a temporary name cannot be allocated.
+ */
+ RefDirectoryUpdate newTemporaryUpdate() throws IOException {
+ File tmp = File.createTempFile("renamed_", "_ref", refsDir);
+ String name = Constants.R_REFS + tmp.getName();
+ Ref ref = new ObjectIdRef.Unpeeled(NEW, name, null);
+ return new RefDirectoryUpdate(this, ref);
+ }
+
+ /**
+ * Locate the file on disk for a single reference name.
+ *
+ * @param name
+ * name of the ref, relative to the Git repository top level
+ * directory (so typically starts with refs/).
+ * @return the loose file location.
+ */
+ File fileFor(String name) {
+ if (name.startsWith(R_REFS)) {
+ name = name.substring(R_REFS.length());
+ return new File(refsDir, name);
+ }
+ return new File(gitDir, name);
+ }
+
+ /**
+ * Locate the log file on disk for a single reference name.
+ *
+ * @param name
+ * name of the ref, relative to the Git repository top level
+ * directory (so typically starts with refs/).
+ * @return the log file location.
+ */
+ File logFor(String name) {
+ if (name.startsWith(R_REFS)) {
+ name = name.substring(R_REFS.length());
+ return new File(logsRefsDir, name);
+ }
+ return new File(logsDir, name);
+ }
+
+ static int levelsIn(final String name) {
+ int count = 0;
+ for (int p = name.indexOf('/'); p >= 0; p = name.indexOf('/', p + 1))
+ count++;
+ return count;
+ }
+
+ static void delete(final File file, final int depth) throws IOException {
+ if (!file.delete() && file.isFile())
+ throw new IOException("File cannot be deleted: " + file);
+
+ File dir = file.getParentFile();
+ for (int i = 0; i < depth; ++i) {
+ if (!dir.delete())
+ break; // ignore problem here
+ dir = dir.getParentFile();
+ }
+ }
+
+ private static class PackedRefList extends RefList<Ref> {
+ static final PackedRefList NO_PACKED_REFS = new PackedRefList(RefList
+ .emptyList(), 0, 0);
+
+ /** Last length of the packed-refs file when we read it. */
+ final long lastSize;
+
+ /** Last modified time of the packed-refs file when we read it. */
+ final long lastModified;
+
+ PackedRefList(RefList<Ref> src, long size, long mtime) {
+ super(src);
+ lastSize = size;
+ lastModified = mtime;
+ }
+ }
+
+ private static LooseSymbolicRef newSymbolicRef(long lastModified,
+ String name, String target) {
+ Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
+ return new LooseSymbolicRef(lastModified, name, dst);
+ }
+
+ private static interface LooseRef extends Ref {
+ long getLastModified();
+
+ LooseRef peel(ObjectIdRef newLeaf);
+ }
+
+ private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag
+ implements LooseRef {
+ private final long lastModified;
+
+ LoosePeeledTag(long mtime, String refName, ObjectId id, ObjectId p) {
+ super(LOOSE, refName, id, p);
+ this.lastModified = mtime;
+ }
+
+ public long getLastModified() {
+ return lastModified;
+ }
+
+ public LooseRef peel(ObjectIdRef newLeaf) {
+ return this;
+ }
+ }
+
+ private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag
+ implements LooseRef {
+ private final long lastModified;
+
+ LooseNonTag(long mtime, String refName, ObjectId id) {
+ super(LOOSE, refName, id);
+ this.lastModified = mtime;
+ }
+
+ public long getLastModified() {
+ return lastModified;
+ }
+
+ public LooseRef peel(ObjectIdRef newLeaf) {
+ return this;
+ }
+ }
+
+ private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled
+ implements LooseRef {
+ private final long lastModified;
+
+ LooseUnpeeled(long mtime, String refName, ObjectId id) {
+ super(LOOSE, refName, id);
+ this.lastModified = mtime;
+ }
+
+ public long getLastModified() {
+ return lastModified;
+ }
+
+ public LooseRef peel(ObjectIdRef newLeaf) {
+ if (newLeaf.getPeeledObjectId() != null)
+ return new LoosePeeledTag(lastModified, getName(),
+ getObjectId(), newLeaf.getPeeledObjectId());
+ else
+ return new LooseNonTag(lastModified, getName(), getObjectId());
+ }
+ }
+
+ private final static class LooseSymbolicRef extends SymbolicRef implements
+ LooseRef {
+ private final long lastModified;
+
+ LooseSymbolicRef(long mtime, String refName, Ref target) {
+ super(refName, target);
+ this.lastModified = mtime;
+ }
+
+ public long getLastModified() {
+ return lastModified;
+ }
+
+ public LooseRef peel(ObjectIdRef newLeaf) {
+ // We should never try to peel the symbolic references.
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java
new file mode 100644
index 0000000000..fec00d9325
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * Copyright (C) 2009, Robin Rosenberg
+ * 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.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/**
+ * Rename any reference stored by {@link RefDirectory}.
+ * <p>
+ * This class works by first renaming the source reference to a temporary name,
+ * then renaming the temporary name to the final destination reference.
+ * <p>
+ * This strategy permits switching a reference like {@code refs/heads/foo},
+ * which is a file, to {@code refs/heads/foo/bar}, which is stored inside a
+ * directory that happens to match the source name.
+ */
+class RefDirectoryRename extends RefRename {
+ private final RefDirectory refdb;
+
+ /**
+ * The value of the source reference at the start of the rename.
+ * <p>
+ * At the end of the rename the destination reference must have this same
+ * value, otherwise we have a concurrent update and the rename must fail
+ * without making any changes.
+ */
+ private ObjectId objId;
+
+ /** True if HEAD must be moved to the destination reference. */
+ private boolean updateHEAD;
+
+ /** A reference we backup {@link #objId} into during the rename. */
+ private RefDirectoryUpdate tmp;
+
+ RefDirectoryRename(RefDirectoryUpdate src, RefDirectoryUpdate dst) {
+ super(src, dst);
+ refdb = src.getRefDatabase();
+ }
+
+ @Override
+ protected Result doRename() throws IOException {
+ if (source.getRef().isSymbolic())
+ return Result.IO_FAILURE; // not supported
+
+ final RevWalk rw = new RevWalk(refdb.getRepository());
+ objId = source.getOldObjectId();
+ updateHEAD = needToUpdateHEAD();
+ tmp = refdb.newTemporaryUpdate();
+ try {
+ // First backup the source so its never unreachable.
+ tmp.setNewObjectId(objId);
+ tmp.setForceUpdate(true);
+ tmp.disableRefLog();
+ switch (tmp.update(rw)) {
+ case NEW:
+ case FORCED:
+ case NO_CHANGE:
+ break;
+ default:
+ return tmp.getResult();
+ }
+
+ // Save the source's log under the temporary name, we must do
+ // this before we delete the source, otherwise we lose the log.
+ if (!renameLog(source, tmp))
+ return Result.IO_FAILURE;
+
+ // If HEAD has to be updated, link it now to destination.
+ // We have to link before we delete, otherwise the delete
+ // fails because its the current branch.
+ RefUpdate dst = destination;
+ if (updateHEAD) {
+ if (!linkHEAD(destination)) {
+ renameLog(tmp, source);
+ return Result.LOCK_FAILURE;
+ }
+
+ // Replace the update operation so HEAD will log the rename.
+ dst = refdb.newUpdate(Constants.HEAD, false);
+ dst.setRefLogIdent(destination.getRefLogIdent());
+ dst.setRefLogMessage(destination.getRefLogMessage(), false);
+ }
+
+ // Delete the source name so its path is free for replacement.
+ source.setExpectedOldObjectId(objId);
+ source.setForceUpdate(true);
+ source.disableRefLog();
+ if (source.delete(rw) != Result.FORCED) {
+ renameLog(tmp, source);
+ if (updateHEAD)
+ linkHEAD(source);
+ return source.getResult();
+ }
+
+ // Move the log to the destination.
+ if (!renameLog(tmp, destination)) {
+ renameLog(tmp, source);
+ source.setExpectedOldObjectId(ObjectId.zeroId());
+ source.setNewObjectId(objId);
+ source.update(rw);
+ if (updateHEAD)
+ linkHEAD(source);
+ return Result.IO_FAILURE;
+ }
+
+ // Create the destination, logging the rename during the creation.
+ dst.setExpectedOldObjectId(ObjectId.zeroId());
+ dst.setNewObjectId(objId);
+ if (dst.update(rw) != Result.NEW) {
+ // If we didn't create the destination we have to undo
+ // our work. Put the log back and restore source.
+ if (renameLog(destination, tmp))
+ renameLog(tmp, source);
+ source.setExpectedOldObjectId(ObjectId.zeroId());
+ source.setNewObjectId(objId);
+ source.update(rw);
+ if (updateHEAD)
+ linkHEAD(source);
+ return dst.getResult();
+ }
+
+ return Result.RENAMED;
+ } finally {
+ // Always try to free the temporary name.
+ try {
+ refdb.delete(tmp);
+ } catch (IOException err) {
+ refdb.fileFor(tmp.getName()).delete();
+ }
+ }
+ }
+
+ private boolean renameLog(RefUpdate src, RefUpdate dst) {
+ File srcLog = refdb.logFor(src.getName());
+ File dstLog = refdb.logFor(dst.getName());
+
+ if (!srcLog.exists())
+ return true;
+
+ if (!rename(srcLog, dstLog))
+ return false;
+
+ try {
+ final int levels = RefDirectory.levelsIn(src.getName()) - 2;
+ RefDirectory.delete(srcLog, levels);
+ return true;
+ } catch (IOException e) {
+ rename(dstLog, srcLog);
+ return false;
+ }
+ }
+
+ private static boolean rename(File src, File dst) {
+ if (src.renameTo(dst))
+ return true;
+
+ File dir = dst.getParentFile();
+ if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory())
+ return false;
+ return src.renameTo(dst);
+ }
+
+ private boolean linkHEAD(RefUpdate target) {
+ try {
+ RefUpdate u = refdb.newUpdate(Constants.HEAD, false);
+ u.disableRefLog();
+ switch (u.link(target.getName())) {
+ case NEW:
+ case FORCED:
+ case NO_CHANGE:
+ return true;
+ default:
+ return false;
+ }
+ } catch (IOException e) {
+ return false;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java
new file mode 100644
index 0000000000..447be104ab
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
+ * 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.eclipse.jgit.lib.Constants.encode;
+
+import java.io.IOException;
+
+/** Updates any reference stored by {@link RefDirectory}. */
+class RefDirectoryUpdate extends RefUpdate {
+ private final RefDirectory database;
+
+ private LockFile lock;
+
+ RefDirectoryUpdate(final RefDirectory r, final Ref ref) {
+ super(ref);
+ database = r;
+ }
+
+ @Override
+ protected RefDirectory getRefDatabase() {
+ return database;
+ }
+
+ @Override
+ protected Repository getRepository() {
+ return database.getRepository();
+ }
+
+ @Override
+ protected boolean tryLock(boolean deref) throws IOException {
+ Ref dst = getRef();
+ if (deref)
+ dst = dst.getLeaf();
+ String name = dst.getName();
+ lock = new LockFile(database.fileFor(name));
+ if (lock.lock()) {
+ dst = database.getRef(name);
+ setOldObjectId(dst != null ? dst.getObjectId() : null);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected void unlock() {
+ if (lock != null) {
+ lock.unlock();
+ lock = null;
+ }
+ }
+
+ @Override
+ protected Result doUpdate(final Result status) throws IOException {
+ lock.setNeedStatInformation(true);
+ lock.write(getNewObjectId());
+
+ String msg = getRefLogMessage();
+ if (msg != null) {
+ if (isRefLogIncludingResult()) {
+ String strResult = toResultString(status);
+ if (strResult != null) {
+ if (msg.length() > 0)
+ msg = msg + ": " + strResult;
+ else
+ msg = strResult;
+ }
+ }
+ database.log(this, msg, true);
+ }
+ if (!lock.commit())
+ return Result.LOCK_FAILURE;
+ database.stored(this, lock.getCommitLastModified());
+ return status;
+ }
+
+ private String toResultString(final Result status) {
+ switch (status) {
+ case FORCED:
+ return "forced-update";
+ case FAST_FORWARD:
+ return "fast forward";
+ case NEW:
+ return "created";
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ protected Result doDelete(final Result status) throws IOException {
+ if (getRef().getLeaf().getStorage() != Ref.Storage.NEW)
+ database.delete(this);
+ return status;
+ }
+
+ @Override
+ protected Result doLink(final String target) throws IOException {
+ lock.setNeedStatInformation(true);
+ lock.write(encode(RefDirectory.SYMREF + target + '\n'));
+
+ String msg = getRefLogMessage();
+ if (msg != null)
+ database.log(this, msg, false);
+ if (!lock.commit())
+ return Result.LOCK_FAILURE;
+ database.storedSymbolicRef(this, lock.getCommitLastModified(), target);
+
+ if (getRef().getStorage() == Ref.Storage.NEW)
+ return Result.NEW;
+ return Result.FORCED;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java
deleted file mode 100644
index 3049685573..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
- * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
- * Copyright (C) 2009, Google Inc.
- * Copyright (C) 2007-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
- * 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.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-/**
- * Utility class to work with reflog files
- *
- * @author Dave Watson
- */
-public class RefLogWriter {
- static void append(final RefUpdate u, final String msg) throws IOException {
- final ObjectId oldId = u.getOldObjectId();
- final ObjectId newId = u.getNewObjectId();
- final Repository db = u.getRepository();
- final PersonIdent ident = u.getRefLogIdent();
-
- appendOneRecord(oldId, newId, ident, msg, db, u.getName());
- if (!u.getName().equals(u.getOrigName()))
- appendOneRecord(oldId, newId, ident, msg, db, u.getOrigName());
- }
-
- static void append(RefRename refRename, String logName, String msg) throws IOException {
- final ObjectId id = refRename.getObjectId();
- final Repository db = refRename.getRepository();
- final PersonIdent ident = refRename.getRefLogIdent();
- appendOneRecord(id, id, ident, msg, db, logName);
- }
-
- static void renameTo(final Repository db, final RefUpdate from,
- final RefUpdate to) throws IOException {
- final File logdir = new File(db.getDirectory(), Constants.LOGS);
- final File reflogFrom = new File(logdir, from.getName());
- if (!reflogFrom.exists())
- return;
- final File reflogTo = new File(logdir, to.getName());
- final File reflogToDir = reflogTo.getParentFile();
- File tmp = new File(logdir, "tmp-renamed-log.." + Thread.currentThread().getId());
- if (!reflogFrom.renameTo(tmp)) {
- throw new IOException("Cannot rename " + reflogFrom + " to (" + tmp
- + ")" + reflogTo);
- }
- RefUpdate.deleteEmptyDir(reflogFrom, RefUpdate.count(from.getName(),
- '/'));
- if (!reflogToDir.exists() && !reflogToDir.mkdirs()) {
- throw new IOException("Cannot create directory " + reflogToDir);
- }
- if (!tmp.renameTo(reflogTo)) {
- throw new IOException("Cannot rename (" + tmp + ")" + reflogFrom
- + " to " + reflogTo);
- }
- }
-
- private static void appendOneRecord(final ObjectId oldId,
- final ObjectId newId, PersonIdent ident, final String msg,
- final Repository db, final String refName) throws IOException {
- if (ident == null)
- ident = new PersonIdent(db);
- else
- ident = new PersonIdent(ident);
-
- final StringBuilder r = new StringBuilder();
- r.append(ObjectId.toString(oldId));
- r.append(' ');
- r.append(ObjectId.toString(newId));
- r.append(' ');
- r.append(ident.toExternalString());
- r.append('\t');
- r.append(msg);
- r.append('\n');
-
- final byte[] rec = Constants.encode(r.toString());
- final File logdir = new File(db.getDirectory(), Constants.LOGS);
- final File reflog = new File(logdir, refName);
- if (reflog.exists() || db.getConfig().getCore().isLogAllRefUpdates()) {
- final File refdir = reflog.getParentFile();
-
- if (!refdir.exists() && !refdir.mkdirs())
- throw new IOException("Cannot create directory " + refdir);
-
- final FileOutputStream out = new FileOutputStream(reflog, true);
- try {
- out.write(rec);
- } finally {
- out.close();
- }
- }
- }
-
- /**
- * Writes reflog entry for ref specified by refName
- *
- * @param repo
- * repository to use
- * @param oldCommit
- * previous commit
- * @param commit
- * new commit
- * @param message
- * reflog message
- * @param refName
- * full ref name
- * @throws IOException
- * @deprecated rely upon {@link RefUpdate}'s automatic logging instead.
- */
- public static void writeReflog(Repository repo, ObjectId oldCommit,
- ObjectId commit, String message, String refName) throws IOException {
- appendOneRecord(oldCommit, commit, null, message, repo, refName);
- }
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
index ba7f208f34..2ff5c614b5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
@@ -1,6 +1,8 @@
/*
+ * Copyright (C) 2009-2010, Google Inc.
* Copyright (C) 2009, Robin Rosenberg
* Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -49,25 +51,96 @@ import java.io.IOException;
import org.eclipse.jgit.lib.RefUpdate.Result;
/**
- * A RefUpdate combination for renaming a ref
+ * A RefUpdate combination for renaming a reference.
+ * <p>
+ * If the source reference is currently pointed to by {@code HEAD}, then the
+ * HEAD symbolic reference is updated to point to the new destination.
*/
-public class RefRename {
- private RefUpdate newToUpdate;
+public abstract class RefRename {
+ /** Update operation to read and delete the source reference. */
+ protected final RefUpdate source;
- private RefUpdate oldFromDelete;
+ /** Update operation to create/overwrite the destination reference. */
+ protected final RefUpdate destination;
- private Result renameResult = Result.NOT_ATTEMPTED;
+ private Result result = Result.NOT_ATTEMPTED;
- RefRename(final RefUpdate toUpdate, final RefUpdate fromUpdate) {
- newToUpdate = toUpdate;
- oldFromDelete = fromUpdate;
+ /**
+ * Initialize a new rename operation.
+ *
+ * @param src
+ * operation to read and delete the source.
+ * @param dst
+ * operation to create (or overwrite) the destination.
+ */
+ protected RefRename(final RefUpdate src, final RefUpdate dst) {
+ source = src;
+ destination = dst;
+
+ Repository repo = destination.getRepository();
+ String cmd = "";
+ if (source.getName().startsWith(Constants.R_HEADS)
+ && destination.getName().startsWith(Constants.R_HEADS))
+ cmd = "Branch: ";
+ setRefLogMessage(cmd + "renamed "
+ + repo.shortenRefName(source.getName()) + " to "
+ + repo.shortenRefName(destination.getName()));
+ }
+
+ /** @return identity of the user making the change in the reflog. */
+ public PersonIdent getRefLogIdent() {
+ return destination.getRefLogIdent();
+ }
+
+ /**
+ * Set the identity of the user appearing in the reflog.
+ * <p>
+ * The timestamp portion of the identity is ignored. A new identity with the
+ * current timestamp will be created automatically when the rename occurs
+ * and the log record is written.
+ *
+ * @param pi
+ * identity of the user. If null the identity will be
+ * automatically determined based on the repository
+ * configuration.
+ */
+ public void setRefLogIdent(final PersonIdent pi) {
+ destination.setRefLogIdent(pi);
+ }
+
+ /**
+ * Get the message to include in the reflog.
+ *
+ * @return message the caller wants to include in the reflog; null if the
+ * rename should not be logged.
+ */
+ public String getRefLogMessage() {
+ return destination.getRefLogMessage();
+ }
+
+ /**
+ * Set the message to include in the reflog.
+ *
+ * @param msg
+ * the message to describe this change.
+ */
+ public void setRefLogMessage(final String msg) {
+ if (msg == null)
+ disableRefLog();
+ else
+ destination.setRefLogMessage(msg, false);
+ }
+
+ /** Don't record this rename in the ref's associated reflog. */
+ public void disableRefLog() {
+ destination.setRefLogMessage("", false);
}
/**
* @return result of rename operation
*/
public Result getResult() {
- return renameResult;
+ return result;
}
/**
@@ -75,101 +148,33 @@ public class RefRename {
* @throws IOException
*/
public Result rename() throws IOException {
- Ref oldRef = oldFromDelete.db.readRef(Constants.HEAD);
- boolean renameHEADtoo = oldRef != null
- && oldRef.getName().equals(oldFromDelete.getName());
- Repository db = oldFromDelete.getRepository();
try {
- RefLogWriter.renameTo(db, oldFromDelete,
- newToUpdate);
- newToUpdate.setRefLogMessage(null, false);
- String tmpRefName = "RENAMED-REF.." + Thread.currentThread().getId();
- RefUpdate tmpUpdateRef = db.updateRef(tmpRefName);
- if (renameHEADtoo) {
- try {
- oldFromDelete.db.link(Constants.HEAD, tmpRefName);
- } catch (IOException e) {
- RefLogWriter.renameTo(db,
- newToUpdate, oldFromDelete);
- return renameResult = Result.LOCK_FAILURE;
- }
- }
- tmpUpdateRef.setNewObjectId(oldFromDelete.getOldObjectId());
- tmpUpdateRef.setForceUpdate(true);
- Result update = tmpUpdateRef.update();
- if (update != Result.FORCED && update != Result.NEW && update != Result.NO_CHANGE) {
- RefLogWriter.renameTo(db,
- newToUpdate, oldFromDelete);
- if (renameHEADtoo) {
- oldFromDelete.db.link(Constants.HEAD, oldFromDelete.getName());
- }
- return renameResult = update;
- }
-
- oldFromDelete.setExpectedOldObjectId(oldFromDelete.getOldObjectId());
- oldFromDelete.setForceUpdate(true);
- Result delete = oldFromDelete.delete();
- if (delete != Result.FORCED) {
- if (db.getRef(
- oldFromDelete.getName()) != null) {
- RefLogWriter.renameTo(db,
- newToUpdate, oldFromDelete);
- if (renameHEADtoo) {
- oldFromDelete.db.link(Constants.HEAD, oldFromDelete
- .getName());
- }
- }
- return renameResult = delete;
- }
-
- newToUpdate.setNewObjectId(tmpUpdateRef.getNewObjectId());
- Result updateResult = newToUpdate.update();
- if (updateResult != Result.NEW) {
- RefLogWriter.renameTo(db, newToUpdate, oldFromDelete);
- if (renameHEADtoo) {
- oldFromDelete.db.link(Constants.HEAD, oldFromDelete.getName());
- }
- oldFromDelete.setExpectedOldObjectId(null);
- oldFromDelete.setNewObjectId(oldFromDelete.getOldObjectId());
- oldFromDelete.setForceUpdate(true);
- oldFromDelete.setRefLogMessage(null, false);
- Result undelete = oldFromDelete.update();
- if (undelete != Result.NEW && undelete != Result.LOCK_FAILURE)
- return renameResult = Result.IO_FAILURE;
- return renameResult = Result.LOCK_FAILURE;
- }
-
- if (renameHEADtoo) {
- oldFromDelete.db.link(Constants.HEAD, newToUpdate.getName());
- } else {
- db.fireRefsMaybeChanged();
- }
- RefLogWriter.append(this, newToUpdate.getName(), "Branch: renamed "
- + db.shortenRefName(oldFromDelete.getName()) + " to "
- + db.shortenRefName(newToUpdate.getName()));
- if (renameHEADtoo)
- RefLogWriter.append(this, Constants.HEAD, "Branch: renamed "
- + db.shortenRefName(oldFromDelete.getName()) + " to "
- + db.shortenRefName(newToUpdate.getName()));
- return renameResult = Result.RENAMED;
- } catch (RuntimeException e) {
- throw e;
+ result = doRename();
+ return result;
+ } catch (IOException err) {
+ result = Result.IO_FAILURE;
+ throw err;
}
}
- ObjectId getObjectId() {
- return oldFromDelete.getOldObjectId();
- }
-
- Repository getRepository() {
- return oldFromDelete.getRepository();
- }
-
- PersonIdent getRefLogIdent() {
- return newToUpdate.getRefLogIdent();
- }
+ /**
+ * @return the result of the rename operation.
+ * @throws IOException
+ */
+ protected abstract Result doRename() throws IOException;
- String getToName() {
- return newToUpdate.getName();
+ /**
+ * @return true if the {@code Constants#HEAD} reference needs to be linked
+ * to the new destination name.
+ * @throws IOException
+ * the current value of {@code HEAD} cannot be read.
+ */
+ protected boolean needToUpdateHEAD() throws IOException {
+ Ref head = source.getRefDatabase().getRef(Constants.HEAD);
+ if (head.isSymbolic()) {
+ head = head.getTarget();
+ return head.getName().equals(source.getName());
+ }
+ return false;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
index 856eb15198..553266284b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -1,6 +1,5 @@
/*
- * Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log.
*
@@ -45,19 +44,17 @@
package org.eclipse.jgit.lib;
-import java.io.File;
import java.io.IOException;
import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
/**
- * Updates any locally stored ref.
+ * Creates, updates or deletes any reference.
*/
-public class RefUpdate {
+public abstract class RefUpdate {
/** Status of an update request. */
public static enum Result {
/** The ref update/delete has not been attempted by the caller. */
@@ -142,12 +139,6 @@ public class RefUpdate {
RENAMED
}
- /** Repository the ref is stored in. */
- final RefDatabase db;
-
- /** Location of the loose file holding the value of this ref. */
- final File looseFile;
-
/** New value the caller wants this ref to have. */
private ObjectId newValue;
@@ -170,22 +161,63 @@ public class RefUpdate {
private ObjectId expValue;
/** Result of the update operation. */
- Result result = Result.NOT_ATTEMPTED;
+ private Result result = Result.NOT_ATTEMPTED;
private final Ref ref;
- RefUpdate(final RefDatabase r, final Ref ref, final File f) {
- db = r;
+ RefUpdate(final Ref ref) {
this.ref = ref;
oldValue = ref.getObjectId();
- looseFile = f;
refLogMessage = "";
}
- /** @return the repository the updated ref resides in */
- public Repository getRepository() {
- return db.getRepository();
- }
+ /** @return the reference database this update modifies. */
+ protected abstract RefDatabase getRefDatabase();
+
+ /** @return the repository storing the database's objects. */
+ protected abstract Repository getRepository();
+
+ /**
+ * Try to acquire the lock on the reference.
+ * <p>
+ * If the locking was successful the implementor must set the current
+ * identity value by calling {@link #setOldObjectId(ObjectId)}.
+ *
+ * @param deref
+ * true if the lock should be taken against the leaf level
+ * reference; false if it should be taken exactly against the
+ * current reference.
+ * @return true if the lock was acquired and the reference is likely
+ * protected from concurrent modification; false if it failed.
+ * @throws IOException
+ * the lock couldn't be taken due to an unexpected storage
+ * failure, and not because of a concurrent update.
+ */
+ protected abstract boolean tryLock(boolean deref) throws IOException;
+
+ /** Releases the lock taken by {@link #tryLock} if it succeeded. */
+ protected abstract void unlock();
+
+ /**
+ * @param desiredResult
+ * @return {@code result}
+ * @throws IOException
+ */
+ protected abstract Result doUpdate(Result desiredResult) throws IOException;
+
+ /**
+ * @param desiredResult
+ * @return {@code result}
+ * @throws IOException
+ */
+ protected abstract Result doDelete(Result desiredResult) throws IOException;
+
+ /**
+ * @param target
+ * @return {@link Result#NEW} on success.
+ * @throws IOException
+ */
+ protected abstract Result doLink(String target) throws IOException;
/**
* Get the name of the ref this update will operate on.
@@ -193,16 +225,12 @@ public class RefUpdate {
* @return name of underlying ref.
*/
public String getName() {
- return ref.getName();
+ return getRef().getName();
}
- /**
- * Get the requested name of the ref thit update will operate on
- *
- * @return original (requested) name of the underlying ref.
- */
- public String getOrigName() {
- return ref.getOrigName();
+ /** @return the reference this update will create or modify. */
+ public Ref getRef() {
+ return ref;
}
/**
@@ -295,12 +323,17 @@ public class RefUpdate {
return refLogMessage;
}
+ /** @return {@code true} if the ref log message should show the result. */
+ protected boolean isRefLogIncludingResult() {
+ return refLogIncludeResult;
+ }
+
/**
* Set the message to include in the reflog.
*
* @param msg
- * the message to describe this change. It may be null
- * if appendStatus is null in order not to append to the reflog
+ * the message to describe this change. It may be null if
+ * appendStatus is null in order not to append to the reflog
* @param appendStatus
* true if the status of the ref change (fast-forward or
* forced-update) should be appended to the user supplied
@@ -340,6 +373,16 @@ public class RefUpdate {
}
/**
+ * Set the old value of the ref.
+ *
+ * @param old
+ * the old value.
+ */
+ protected void setOldObjectId(ObjectId old) {
+ oldValue = old;
+ }
+
+ /**
* Get the status of this update.
* <p>
* The same value that was previously returned from an update method.
@@ -378,7 +421,7 @@ public class RefUpdate {
* This is the same as:
*
* <pre>
- * return update(new RevWalk(repository));
+ * return update(new RevWalk(getRepository()));
* </pre>
*
* @return the result status of the update.
@@ -386,7 +429,7 @@ public class RefUpdate {
* an unexpected IO error occurred while writing changes.
*/
public Result update() throws IOException {
- return update(new RevWalk(db.getRepository()));
+ return update(new RevWalk(getRepository()));
}
/**
@@ -404,7 +447,14 @@ public class RefUpdate {
public Result update(final RevWalk walk) throws IOException {
requireCanDoUpdate();
try {
- return result = updateImpl(walk, new UpdateStore());
+ return result = updateImpl(walk, new Store() {
+ @Override
+ Result execute(Result status) throws IOException {
+ if (status == Result.NO_CHANGE)
+ return status;
+ return doUpdate(status);
+ }
+ });
} catch (IOException x) {
result = Result.IO_FAILURE;
throw x;
@@ -417,14 +467,14 @@ public class RefUpdate {
* This is the same as:
*
* <pre>
- * return delete(new RevWalk(repository));
+ * return delete(new RevWalk(getRepository()));
* </pre>
*
* @return the result status of the delete.
* @throws IOException
*/
public Result delete() throws IOException {
- return delete(new RevWalk(db.getRepository()));
+ return delete(new RevWalk(getRepository()));
}
/**
@@ -437,33 +487,83 @@ public class RefUpdate {
* @throws IOException
*/
public Result delete(final RevWalk walk) throws IOException {
- if (getName().startsWith(Constants.R_HEADS)) {
- final Ref head = db.readRef(Constants.HEAD);
- if (head != null && getName().equals(head.getName()))
- return result = Result.REJECTED_CURRENT_BRANCH;
+ final String myName = getRef().getLeaf().getName();
+ if (myName.startsWith(Constants.R_HEADS)) {
+ Ref head = getRefDatabase().getRef(Constants.HEAD);
+ while (head.isSymbolic()) {
+ head = head.getTarget();
+ if (myName.equals(head.getName()))
+ return result = Result.REJECTED_CURRENT_BRANCH;
+ }
}
try {
- return result = updateImpl(walk, new DeleteStore());
+ return result = updateImpl(walk, new Store() {
+ @Override
+ Result execute(Result status) throws IOException {
+ return doDelete(status);
+ }
+ });
+ } catch (IOException x) {
+ result = Result.IO_FAILURE;
+ throw x;
+ }
+ }
+
+ /**
+ * Replace this reference with a symbolic reference to another reference.
+ * <p>
+ * This exact reference (not its traversed leaf) is replaced with a symbolic
+ * reference to the requested name.
+ *
+ * @param target
+ * name of the new target for this reference. The new target name
+ * must be absolute, so it must begin with {@code refs/}.
+ * @return {@link Result#NEW} or {@link Result#FORCED} on success.
+ * @throws IOException
+ */
+ public Result link(String target) throws IOException {
+ if (!target.startsWith(Constants.R_REFS))
+ throw new IllegalArgumentException("Not " + Constants.R_REFS);
+ if (getRefDatabase().isNameConflicting(getName()))
+ return Result.LOCK_FAILURE;
+ try {
+ if (!tryLock(false))
+ return Result.LOCK_FAILURE;
+
+ final Ref old = getRefDatabase().getRef(getName());
+ if (old != null && old.isSymbolic()) {
+ final Ref dst = old.getTarget();
+ if (target.equals(dst.getName()))
+ return result = Result.NO_CHANGE;
+ }
+
+ if (old != null && old.getObjectId() != null)
+ setOldObjectId(old.getObjectId());
+
+ final Ref dst = getRefDatabase().getRef(target);
+ if (dst != null && dst.getObjectId() != null)
+ setNewObjectId(dst.getObjectId());
+
+ return result = doLink(target);
} catch (IOException x) {
result = Result.IO_FAILURE;
throw x;
+ } finally {
+ unlock();
}
}
private Result updateImpl(final RevWalk walk, final Store store)
throws IOException {
- final LockFile lock;
RevObject newObj;
RevObject oldObj;
- if (isNameConflicting())
- return Result.LOCK_FAILURE;
- lock = new LockFile(looseFile);
- if (!lock.lock())
+ if (getRefDatabase().isNameConflicting(getName()))
return Result.LOCK_FAILURE;
try {
- oldValue = db.idOf(getName());
+ if (!tryLock(true))
+ return Result.LOCK_FAILURE;
if (expValue != null) {
final ObjectId o;
o = oldValue != null ? oldValue : ObjectId.zeroId();
@@ -471,41 +571,26 @@ public class RefUpdate {
return Result.LOCK_FAILURE;
}
if (oldValue == null)
- return store.store(lock, Result.NEW);
+ return store.execute(Result.NEW);
newObj = safeParse(walk, newValue);
oldObj = safeParse(walk, oldValue);
if (newObj == oldObj)
- return store.store(lock, Result.NO_CHANGE);
+ return store.execute(Result.NO_CHANGE);
if (newObj instanceof RevCommit && oldObj instanceof RevCommit) {
if (walk.isMergedInto((RevCommit) oldObj, (RevCommit) newObj))
- return store.store(lock, Result.FAST_FORWARD);
+ return store.execute(Result.FAST_FORWARD);
}
if (isForceUpdate())
- return store.store(lock, Result.FORCED);
+ return store.execute(Result.FORCED);
return Result.REJECTED;
} finally {
- lock.unlock();
+ unlock();
}
}
- private boolean isNameConflicting() throws IOException {
- final String myName = getName();
- final int lastSlash = myName.lastIndexOf('/');
- if (lastSlash > 0)
- if (db.getRepository().getRef(myName.substring(0, lastSlash)) != null)
- return true;
-
- final String rName = myName + "/";
- for (Ref r : db.getAllRefs().values()) {
- if (r.getName().startsWith(rName))
- return true;
- }
- return false;
- }
-
private static RevObject safeParse(final RevWalk rw, final AnyObjectId id)
throws IOException {
try {
@@ -520,123 +605,11 @@ public class RefUpdate {
}
}
- private Result updateStore(final LockFile lock, final Result status)
- throws IOException {
- if (status == Result.NO_CHANGE)
- return status;
- lock.setNeedStatInformation(true);
- lock.write(newValue);
- String msg = getRefLogMessage();
- if (msg != null) {
- if (refLogIncludeResult) {
- String strResult = toResultString(status);
- if (strResult != null) {
- if (msg.length() > 0)
- msg = msg + ": " + strResult;
- else
- msg = strResult;
- }
- }
- RefLogWriter.append(this, msg);
- }
- if (!lock.commit())
- return Result.LOCK_FAILURE;
- db.stored(this.ref.getOrigName(), ref.getName(), newValue, lock.getCommitLastModified());
- return status;
- }
-
- private static String toResultString(final Result status) {
- switch (status) {
- case FORCED:
- return "forced-update";
- case FAST_FORWARD:
- return "fast forward";
- case NEW:
- return "created";
- default:
- return null;
- }
- }
-
/**
* Handle the abstraction of storing a ref update. This is because both
* updating and deleting of a ref have merge testing in common.
*/
private abstract class Store {
- abstract Result store(final LockFile lock, final Result status)
- throws IOException;
- }
-
- class UpdateStore extends Store {
-
- @Override
- Result store(final LockFile lock, final Result status)
- throws IOException {
- return updateStore(lock, status);
- }
- }
-
- class DeleteStore extends Store {
-
- @Override
- Result store(LockFile lock, Result status) throws IOException {
- Storage storage = ref.getStorage();
- if (storage == Storage.NEW)
- return status;
- if (storage.isPacked())
- db.removePackedRef(ref.getName());
-
- final int levels = count(ref.getName(), '/') - 2;
-
- // Delete logs _before_ unlocking
- final File gitDir = db.getRepository().getDirectory();
- final File logDir = new File(gitDir, Constants.LOGS);
- deleteFileAndEmptyDir(new File(logDir, ref.getName()), levels);
-
- // We have to unlock before (maybe) deleting the parent directories
- lock.unlock();
- if (storage.isLoose())
- deleteFileAndEmptyDir(looseFile, levels);
- db.uncacheRef(ref.getName());
- return status;
- }
-
- private void deleteFileAndEmptyDir(final File file, final int depth)
- throws IOException {
- if (file.isFile()) {
- if (!file.delete())
- throw new IOException("File cannot be deleted: " + file);
- File dir = file.getParentFile();
- for (int i = 0; i < depth; ++i) {
- if (!dir.delete())
- break; // ignore problem here
- dir = dir.getParentFile();
- }
- }
- }
- }
-
- UpdateStore newUpdateStore() {
- return new UpdateStore();
- }
-
- DeleteStore newDeleteStore() {
- return new DeleteStore();
- }
-
- static void deleteEmptyDir(File dir, int depth) {
- for (; depth > 0 && dir != null; depth--) {
- if (dir.exists() && !dir.delete())
- break;
- dir = dir.getParentFile();
- }
- }
-
- static int count(final String s, final char c) {
- int count = 0;
- for (int p = s.indexOf(c); p >= 0; p = s.indexOf(c, p + 1)) {
- count++;
- }
- return count;
+ abstract Result execute(Result status) throws IOException;
}
}
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 c48449933b..6b0c7b3a98 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
@@ -49,6 +49,10 @@ package org.eclipse.jgit.lib;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Collection;
+import java.util.Map;
+
+import org.eclipse.jgit.util.RefList;
+import org.eclipse.jgit.util.RefMap;
/**
* Writes out refs to the {@link Constants#INFO_REFS} and
@@ -71,6 +75,22 @@ public abstract class RefWriter {
}
/**
+ * @param refs
+ * the complete set of references. This should have been computed
+ * by applying updates to the advertised refs already discovered.
+ */
+ public RefWriter(Map<String, Ref> refs) {
+ if (refs instanceof RefMap)
+ this.refs = refs.values();
+ else
+ this.refs = RefComparator.sort(refs.values());
+ }
+
+ RefWriter(RefList<Ref> list) {
+ this.refs = list.asList();
+ }
+
+ /**
* Rebuild the {@link Constants#INFO_REFS}.
* <p>
* This method rebuilds the contents of the {@link Constants#INFO_REFS} file
@@ -85,7 +105,7 @@ public abstract class RefWriter {
final StringWriter w = new StringWriter();
final char[] tmp = new char[Constants.OBJECT_ID_STRING_LENGTH];
for (final Ref r : refs) {
- if (Constants.HEAD.equals(r.getOrigName())) {
+ if (Constants.HEAD.equals(r.getName())) {
// Historically HEAD has never been published through
// the INFO_REFS file. This is a mistake, but its the
// way things are.
@@ -121,19 +141,18 @@ public abstract class RefWriter {
*/
public void writePackedRefs() throws IOException {
boolean peeled = false;
-
for (final Ref r : refs) {
- if (r.getStorage() != Ref.Storage.PACKED)
- continue;
- if (r.getPeeledObjectId() != null)
+ if (r.getStorage().isPacked() && r.isPeeled()) {
peeled = true;
+ break;
+ }
}
final StringWriter w = new StringWriter();
if (peeled) {
- w.write("# pack-refs with:");
+ w.write(RefDirectory.PACKED_REFS_HEADER);
if (peeled)
- w.write(" peeled");
+ w.write(RefDirectory.PACKED_REFS_PEELED);
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 91a97740e5..45eda052ad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
+ * Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2006-2010, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
* and other copyright owners as documented in the project's IP log.
@@ -45,10 +46,7 @@
package org.eclipse.jgit.lib;
-import java.io.BufferedReader;
import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -223,7 +221,7 @@ public class Repository {
}
}
- refs = new RefDatabase(this);
+ refs = new RefDirectory(this);
if (objectDir != null)
objectDatabase = new ObjectDirectory(FS.resolve(objectDir, ""),
alternateObjectDir);
@@ -278,8 +276,10 @@ public class Repository {
objectDatabase.create();
new File(gitDir, "branches").mkdir();
- final String master = Constants.R_HEADS + Constants.MASTER;
- refs.link(Constants.HEAD, master);
+
+ RefUpdate head = updateRef(Constants.HEAD);
+ head.disableRefLog();
+ head.link(Constants.R_HEADS + Constants.MASTER);
cfg.setInt("core", null, "repositoryformatversion", 0);
cfg.setBoolean("core", null, "filemode", true);
@@ -310,6 +310,11 @@ public class Repository {
return objectDatabase;
}
+ /** @return the reference database which stores the reference namespace. */
+ public RefDatabase getRefDatabase() {
+ return refs;
+ }
+
/**
* @return the configuration of this repository
*/
@@ -591,7 +596,7 @@ public class Repository {
* to the base ref, as the symbolic ref could not be read.
*/
public RefUpdate updateRef(final String ref) throws IOException {
- return refs.newUpdate(ref);
+ return updateRef(ref, false);
}
/**
@@ -862,7 +867,7 @@ public class Repository {
private ObjectId resolveSimple(final String revstr) throws IOException {
if (ObjectId.isId(revstr))
return ObjectId.fromString(revstr);
- final Ref r = refs.readRef(revstr);
+ final Ref r = refs.getRef(revstr);
return r != null ? r.getObjectId() : null;
}
@@ -875,8 +880,10 @@ public class Repository {
* Close all resources used by this repository
*/
public void close() {
- if (useCnt.decrementAndGet() == 0)
+ if (useCnt.decrementAndGet() == 0) {
objectDatabase.close();
+ refs.close();
+ }
}
/**
@@ -894,70 +901,53 @@ public class Repository {
objectDatabase.openPack(pack, idx);
}
- /**
- * Writes a symref (e.g. HEAD) to disk
- *
- * @param name symref name
- * @param target pointed to ref
- * @throws IOException
- */
- public void writeSymref(final String name, final String target)
- throws IOException {
- refs.link(name, target);
- }
-
public String toString() {
return "Repository[" + getDirectory() + "]";
}
/**
- * @return name of current branch
+ * Get the name of the reference that {@code HEAD} points to.
+ * <p>
+ * This is essentially the same as doing:
+ *
+ * <pre>
+ * return getRef(Constants.HEAD).getTarget().getName()
+ * </pre>
+ *
+ * Except when HEAD is detached, in which case this method returns the
+ * current ObjectId in hexadecimal string format.
+ *
+ * @return name of current branch (for example {@code refs/heads/master}) or
+ * an ObjectId in hex format if the current branch is detached.
* @throws IOException
*/
public String getFullBranch() throws IOException {
- final File ptr = new File(getDirectory(),Constants.HEAD);
- final BufferedReader br = new BufferedReader(new FileReader(ptr));
- String ref;
- try {
- ref = br.readLine();
- } finally {
- br.close();
- }
- if (ref.startsWith("ref: "))
- ref = ref.substring(5);
- return ref;
+ Ref head = getRef(Constants.HEAD);
+ if (head == null)
+ return null;
+ if (head.isSymbolic())
+ return head.getTarget().getName();
+ if (head.getObjectId() != null)
+ return head.getObjectId().name();
+ return null;
}
/**
- * @return name of current branch.
+ * Get the short name of the current branch that {@code HEAD} points to.
+ * <p>
+ * This is essentially the same as {@link #getFullBranch()}, except the
+ * leading prefix {@code refs/heads/} is removed from the reference before
+ * it is returned to the caller.
+ *
+ * @return name of current branch (for example {@code master}), or an
+ * ObjectId in hex format if the current branch is detached.
* @throws IOException
*/
public String getBranch() throws IOException {
- try {
- final File ptr = new File(getDirectory(), Constants.HEAD);
- final BufferedReader br = new BufferedReader(new FileReader(ptr));
- String ref;
- try {
- ref = br.readLine();
- } finally {
- br.close();
- }
- if (ref.startsWith("ref: "))
- ref = ref.substring(5);
- if (ref.startsWith("refs/heads/"))
- ref = ref.substring(11);
- return ref;
- } catch (FileNotFoundException e) {
- final File ptr = new File(getDirectory(),"head-name");
- final BufferedReader br = new BufferedReader(new FileReader(ptr));
- String ref;
- try {
- ref = br.readLine();
- } finally {
- br.close();
- }
- return ref;
- }
+ String name = getFullBranch();
+ if (name != null)
+ return shortenRefName(name);
+ return name;
}
/**
@@ -971,26 +961,35 @@ public class Repository {
* @throws IOException
*/
public Ref getRef(final String name) throws IOException {
- return refs.readRef(name);
+ return refs.getRef(name);
}
/**
- * @return all known refs (heads, tags, remotes).
+ * @return mutable map of all known refs (heads, tags, remotes).
*/
public Map<String, Ref> getAllRefs() {
- return refs.getAllRefs();
+ try {
+ return refs.getRefs(RefDatabase.ALL);
+ } catch (IOException e) {
+ return new HashMap<String, Ref>();
+ }
}
/**
- * @return all tags; key is short tag name ("v1.0") and value of the entry
- * contains the ref with the full tag name ("refs/tags/v1.0").
+ * @return mutable map of all tags; key is short tag name ("v1.0") and value
+ * of the entry contains the ref with the full tag name
+ * ("refs/tags/v1.0").
*/
public Map<String, Ref> getTags() {
- return refs.getTags();
+ try {
+ return refs.getRefs(Constants.R_TAGS);
+ } catch (IOException e) {
+ return new HashMap<String, Ref>();
+ }
}
/**
- * Peel a possibly unpeeled ref and updates it.
+ * Peel a possibly unpeeled reference to an annotated tag.
* <p>
* If the ref cannot be peeled (as it does not refer to an annotated tag)
* the peeled id stays null, but {@link Ref#isPeeled()} will be true.
@@ -1003,7 +1002,14 @@ public class Repository {
* (or null).
*/
public Ref peel(final Ref ref) {
- return refs.peel(ref);
+ try {
+ return refs.peel(ref);
+ } catch (IOException e) {
+ // Historical accident; if the reference cannot be peeled due
+ // to some sort of repository access problem we claim that the
+ // same as if the reference was not an annotated tag.
+ return ref;
+ }
}
/**
@@ -1013,8 +1019,7 @@ public class Repository {
Map<String, Ref> allRefs = getAllRefs();
Map<AnyObjectId, Set<Ref>> ret = new HashMap<AnyObjectId, Set<Ref>>(allRefs.size());
for (Ref ref : allRefs.values()) {
- if (!ref.isPeeled())
- ref = peel(ref);
+ ref = peel(ref);
AnyObjectId target = ref.getPeeledObjectId();
if (target == null)
target = ref.getObjectId();
@@ -1033,11 +1038,6 @@ public class Repository {
return ret;
}
- /** Clean up stale caches */
- public void refreshFromDisk() {
- refs.clearCache();
- }
-
/**
* @return a representation of the index associated with this repo
* @throws IOException
@@ -1115,7 +1115,7 @@ public class Repository {
final int len = refName.length();
if (len == 0)
return false;
- if (refName.endsWith(".lock"))
+ if (refName.endsWith(LockFile.SUFFIX))
return false;
int components = 1;
@@ -1233,20 +1233,17 @@ public class Repository {
allListeners.remove(l);
}
- void fireRefsMaybeChanged() {
- if (refs.lastRefModification != refs.lastNotifiedRefModification) {
- refs.lastNotifiedRefModification = refs.lastRefModification;
- final RefsChangedEvent event = new RefsChangedEvent(this);
- List<RepositoryListener> all;
- synchronized (listeners) {
- all = new ArrayList<RepositoryListener>(listeners);
- }
- synchronized (allListeners) {
- all.addAll(allListeners);
- }
- for (final RepositoryListener l : all) {
- l.refsChanged(event);
- }
+ void fireRefsChanged() {
+ final RefsChangedEvent event = new RefsChangedEvent(this);
+ List<RepositoryListener> all;
+ synchronized (listeners) {
+ all = new ArrayList<RepositoryListener>(listeners);
+ }
+ synchronized (allListeners) {
+ all.addAll(allListeners);
+ }
+ for (final RepositoryListener l : all) {
+ l.refsChanged(event);
}
}
@@ -1298,7 +1295,7 @@ public class Repository {
public ReflogReader getReflogReader(String refName) throws IOException {
Ref ref = getRef(refName);
if (ref != null)
- return new ReflogReader(this, ref.getOrigName());
+ return new ReflogReader(this, ref.getName());
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
new file mode 100644
index 0000000000..d169d61f1c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2010, 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;
+
+/**
+ * A reference that indirectly points at another {@link Ref}.
+ * <p>
+ * A symbolic reference always derives its current value from the target
+ * reference.
+ */
+public class SymbolicRef implements Ref {
+ private final String name;
+
+ private final Ref target;
+
+ /**
+ * Create a new ref pairing.
+ *
+ * @param refName
+ * name of this ref.
+ * @param target
+ * the ref we reference and derive our value from.
+ */
+ public SymbolicRef(String refName, Ref target) {
+ this.name = refName;
+ this.target = target;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isSymbolic() {
+ return true;
+ }
+
+ public Ref getLeaf() {
+ Ref dst = getTarget();
+ while (dst.isSymbolic())
+ dst = dst.getTarget();
+ return dst;
+ }
+
+ public Ref getTarget() {
+ return target;
+ }
+
+ public ObjectId getObjectId() {
+ return getLeaf().getObjectId();
+ }
+
+ public Storage getStorage() {
+ return Storage.LOOSE;
+ }
+
+ public ObjectId getPeeledObjectId() {
+ return getLeaf().getPeeledObjectId();
+ }
+
+ public boolean isPeeled() {
+ return getLeaf().isPeeled();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder r = new StringBuilder();
+ r.append("SymbolicRef[");
+ Ref cur = this;
+ while (cur.isSymbolic()) {
+ r.append(cur.getName());
+ r.append(" -> ");
+ cur = cur.getTarget();
+ }
+ r.append(cur.getName());
+ r.append('=');
+ r.append(ObjectId.toString(cur.getObjectId()));
+ r.append("]");
+ return r.toString();
+ }
+}
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 90634d207a..74c27a7cda 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -61,6 +61,7 @@ import org.eclipse.jgit.errors.NoRemoteRepositoryException;
import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.io.InterruptTimer;
@@ -209,11 +210,11 @@ abstract class BasePackConnection extends BaseConnection {
if (prior.getPeeledObjectId() != null)
throw duplicateAdvertisement(name + "^{}");
- avail.put(name, new Ref(Ref.Storage.NETWORK, name, prior
- .getObjectId(), id, true));
+ avail.put(name, new ObjectIdRef.PeeledTag(
+ Ref.Storage.NETWORK, name, prior.getObjectId(), id));
} else {
- final Ref prior;
- prior = avail.put(name, new Ref(Ref.Storage.NETWORK, name, id));
+ final Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
+ Ref.Storage.NETWORK, name, id));
if (prior != null)
throw duplicateAdvertisement(name);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
index 6819635465..c788244f74 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2009, Sasa Zivkov <sasa.zivkov@sap.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
@@ -65,6 +65,7 @@ import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.PackLock;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
@@ -140,8 +141,8 @@ class BundleFetchConnection extends BaseFetchConnection {
final String name = line.substring(41, line.length());
final ObjectId id = ObjectId.fromString(line.substring(0, 40));
- final Ref prior = avail.put(name, new Ref(Ref.Storage.NETWORK,
- name, id));
+ final Ref prior = avail.put(name, new ObjectIdRef.Unpeeled(
+ Ref.Storage.NETWORK, name, id));
if (prior != null)
throw duplicateAdvertisement(name);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
index f85c22ddb5..15bdf9618f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008-2010, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -52,7 +52,6 @@ import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -590,10 +589,10 @@ public class ReceivePack {
adv.advertiseCapability(CAPABILITY_REPORT_STATUS);
if (allowOfsDelta)
adv.advertiseCapability(CAPABILITY_OFS_DELTA);
- refs = new HashMap<String, Ref>(db.getAllRefs());
+ refs = db.getAllRefs();
final Ref head = refs.remove(Constants.HEAD);
- adv.send(refs.values());
- if (head != null && head.getName().equals(head.getOrigName()))
+ adv.send(refs);
+ if (!head.isSymbolic())
adv.advertiseHave(head.getObjectId());
adv.includeAdditionalHaves();
if (adv.isEmpty())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
index 953fae9184..694a2e0f16 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008-2010, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -44,9 +44,10 @@
package org.eclipse.jgit.transport;
import java.io.IOException;
-import java.util.Collection;
import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Set;
+import java.util.SortedMap;
import org.eclipse.jgit.lib.AlternateRepositoryDatabase;
import org.eclipse.jgit.lib.AnyObjectId;
@@ -59,6 +60,7 @@ import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.util.RefMap;
/** Support for the start of {@link UploadPack} and {@link ReceivePack}. */
public abstract class RefAdvertiser {
@@ -122,7 +124,7 @@ public abstract class RefAdvertiser {
* <p>
* This method must be invoked prior to any of the following:
* <ul>
- * <li>{@link #send(Collection)}
+ * <li>{@link #send(Map)}
* <li>{@link #advertiseHave(AnyObjectId)}
* <li>{@link #includeAdditionalHaves()}
* </ul>
@@ -140,7 +142,7 @@ public abstract class RefAdvertiser {
* <p>
* This method must be invoked prior to any of the following:
* <ul>
- * <li>{@link #send(Collection)}
+ * <li>{@link #send(Map)}
* <li>{@link #advertiseHave(AnyObjectId)}
* <li>{@link #includeAdditionalHaves()}
* </ul>
@@ -160,23 +162,30 @@ public abstract class RefAdvertiser {
*
* @param refs
* zero or more refs to format for the client. The collection is
- * copied and sorted before display and therefore may appear in
- * any order.
+ * sorted before display if necessary, and therefore may appear
+ * in any order.
* @throws IOException
* the underlying output stream failed to write out an
* advertisement record.
*/
- public void send(final Collection<Ref> refs) throws IOException {
- for (final Ref r : RefComparator.sort(refs)) {
+ public void send(final Map<String, Ref> refs) throws IOException {
+ for (final Ref r : getSortedRefs(refs)) {
final RevObject obj = parseAnyOrNull(r.getObjectId());
if (obj != null) {
- advertiseAny(obj, r.getOrigName());
+ advertiseAny(obj, r.getName());
if (derefTags && obj instanceof RevTag)
- advertiseTag((RevTag) obj, r.getOrigName() + "^{}");
+ advertiseTag((RevTag) obj, r.getName() + "^{}");
}
}
}
+ private Iterable<Ref> getSortedRefs(Map<String, Ref> all) {
+ if (all instanceof RefMap
+ || (all instanceof SortedMap && ((SortedMap) all).comparator() == null))
+ return all.values();
+ return RefComparator.sort(all.values());
+ }
+
/**
* Advertise one object is available using the magic {@code .have}.
* <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
index 6a1a17f605..a3fd1ceae5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
@@ -61,9 +61,11 @@ import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.lib.Ref.Storage;
import org.eclipse.jgit.util.FS;
@@ -305,16 +307,15 @@ public class TransportAmazonS3 extends HttpTransport implements WalkTransport {
if (r == null)
r = readRef(avail, target);
if (r == null)
- return null;
- r = new Ref(r.getStorage(), rn, r.getObjectId(), r
- .getPeeledObjectId(), r.isPeeled());
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
+ r = new SymbolicRef(rn, r);
avail.put(r.getName(), r);
return r;
}
if (ObjectId.isId(s)) {
- final Ref r = new Ref(loose(avail.get(rn)), rn, ObjectId
- .fromString(s));
+ final Ref r = new ObjectIdRef.Unpeeled(loose(avail.get(rn)),
+ rn, ObjectId.fromString(s));
avail.put(r.getName(), r);
return r;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
index c521e80514..f041765b8a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -80,9 +80,12 @@ import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDirectory;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.util.HttpSupport;
import org.eclipse.jgit.util.IO;
@@ -240,16 +243,17 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
br = toBufferedReader(openInputStream(conn));
try {
String line = br.readLine();
- if (line != null && line.startsWith("ref: ")) {
- Ref src = refs.get(line.substring(5));
- if (src != null) {
- refs.put(Constants.HEAD, new Ref(
- Ref.Storage.NETWORK, Constants.HEAD, src
- .getName(), src.getObjectId()));
- }
+ if (line != null && line.startsWith(RefDirectory.SYMREF)) {
+ String target = line.substring(RefDirectory.SYMREF.length());
+ Ref r = refs.get(target);
+ if (r == null)
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
+ r = new SymbolicRef(Constants.HEAD, r);
+ refs.put(r.getName(), r);
} else if (line != null && ObjectId.isId(line)) {
- refs.put(Constants.HEAD, new Ref(Ref.Storage.NETWORK,
- Constants.HEAD, ObjectId.fromString(line)));
+ Ref r = new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK,
+ Constants.HEAD, ObjectId.fromString(line));
+ refs.put(r.getName(), r);
}
} finally {
br.close();
@@ -527,10 +531,11 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
if (prior.getPeeledObjectId() != null)
throw duplicateAdvertisement(name + "^{}");
- avail.put(name, new Ref(Ref.Storage.NETWORK, name, prior
- .getObjectId(), id, true));
+ avail.put(name, new ObjectIdRef.PeeledTag(
+ Ref.Storage.NETWORK, name,
+ prior.getObjectId(), id));
} else {
- final Ref prior = avail.put(name, new Ref(
+ Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
Ref.Storage.NETWORK, name, id));
if (prior != null)
throw duplicateAdvertisement(name);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
index 617cca8907..2a196b51d2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
@@ -59,9 +59,11 @@ import java.util.TreeMap;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.lib.Ref.Storage;
import com.jcraft.jsch.Channel;
@@ -389,21 +391,20 @@ public class TransportSftp extends SshTransport implements WalkTransport {
throw new TransportException("Empty ref: " + name);
if (line.startsWith("ref: ")) {
- final String p = line.substring("ref: ".length());
- Ref r = readRef(avail, ROOT_DIR + p, p);
+ final String target = line.substring("ref: ".length());
+ Ref r = avail.get(target);
if (r == null)
- r = avail.get(p);
- if (r != null) {
- r = new Ref(loose(r), name, r.getObjectId(), r
- .getPeeledObjectId(), true);
- avail.put(name, r);
- }
+ r = readRef(avail, ROOT_DIR + target, target);
+ if (r == null)
+ r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
+ r = new SymbolicRef(name, r);
+ avail.put(r.getName(), r);
return r;
}
if (ObjectId.isId(line)) {
- final Ref r = new Ref(loose(avail.get(name)), name, ObjectId
- .fromString(line));
+ final Ref r = new ObjectIdRef.Unpeeled(loose(avail.get(name)),
+ name, ObjectId.fromString(line));
avail.put(r.getName(), r);
return r;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index 6b81bc4925..57bb2adbf8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2008-2010, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -330,7 +330,7 @@ public class UploadPack {
adv.advertiseCapability(OPTION_NO_PROGRESS);
adv.setDerefTags(true);
refs = db.getAllRefs();
- adv.send(refs.values());
+ adv.send(refs);
adv.end();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
index 56f73c50b2..88b7ca438b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
@@ -58,6 +58,7 @@ import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.PackWriter;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
@@ -324,8 +325,8 @@ class WalkPushConnection extends BaseConnection implements PushConnection {
private void updateCommand(final RemoteRefUpdate u) {
try {
dest.writeRef(u.getRemoteName(), u.getNewObjectId());
- newRefs.put(u.getRemoteName(), new Ref(Storage.LOOSE, u
- .getRemoteName(), u.getNewObjectId()));
+ newRefs.put(u.getRemoteName(), new ObjectIdRef.Unpeeled(
+ Storage.LOOSE, u.getRemoteName(), u.getNewObjectId()));
u.setStatus(Status.OK);
} catch (IOException e) {
u.setStatus(Status.REJECTED_OTHER_REASON);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
index 6a010fb4e5..2aa644ce8b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java
@@ -57,8 +57,10 @@ import java.util.Map;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDirectory;
import org.eclipse.jgit.util.IO;
/**
@@ -433,18 +435,24 @@ abstract class WalkRemoteObjectDatabase {
private void readPackedRefsImpl(final Map<String, Ref> avail,
final BufferedReader br) throws IOException {
Ref last = null;
+ boolean peeled = false;
for (;;) {
String line = br.readLine();
if (line == null)
break;
- if (line.charAt(0) == '#')
+ if (line.charAt(0) == '#') {
+ if (line.startsWith(RefDirectory.PACKED_REFS_HEADER)) {
+ line = line.substring(RefDirectory.PACKED_REFS_HEADER.length());
+ peeled = line.contains(RefDirectory.PACKED_REFS_PEELED);
+ }
continue;
+ }
if (line.charAt(0) == '^') {
if (last == null)
throw new TransportException("Peeled line before ref.");
final ObjectId id = ObjectId.fromString(line.substring(1));
- last = new Ref(Ref.Storage.PACKED, last.getName(), last
- .getObjectId(), id, true);
+ last = new ObjectIdRef.PeeledTag(Ref.Storage.PACKED, last
+ .getName(), last.getObjectId(), id);
avail.put(last.getName(), last);
continue;
}
@@ -454,7 +462,10 @@ abstract class WalkRemoteObjectDatabase {
throw new TransportException("Unrecognized ref: " + line);
final ObjectId id = ObjectId.fromString(line.substring(0, sp));
final String name = line.substring(sp + 1);
- last = new Ref(Ref.Storage.PACKED, name, id);
+ if (peeled)
+ last = new ObjectIdRef.PeeledNonTag(Ref.Storage.PACKED, name, id);
+ else
+ last = new ObjectIdRef.Unpeeled(Ref.Storage.PACKED, name, id);
avail.put(last.getName(), last);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
new file mode 100644
index 0000000000..45b065999c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2010, 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 java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefComparator;
+
+/**
+ * Specialized variant of an ArrayList to support a {@code RefDatabase}.
+ * <p>
+ * This list is a hybrid of a Map&lt;String,Ref&gt; and of a List&lt;Ref&gt;. It
+ * tracks reference instances by name by keeping them sorted and performing
+ * binary search to locate an entry. Lookup time is O(log N), but addition and
+ * removal is O(N + log N) due to the list expansion or contraction costs.
+ * <p>
+ * This list type is copy-on-write. Mutation methods return a new copy of the
+ * list, leaving {@code this} unmodified. As a result we cannot easily implement
+ * the {@link java.util.List} interface contract.
+ *
+ * @param <T>
+ * the type of reference being stored in the collection.
+ */
+public class RefList<T extends Ref> implements Iterable<Ref> {
+ private static final RefList<Ref> EMPTY = new RefList<Ref>(new Ref[0], 0);
+
+ /**
+ * @return an empty unmodifiable reference list.
+ * @param <T>
+ * the type of reference being stored in the collection.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends Ref> RefList<T> emptyList() {
+ return (RefList<T>) EMPTY;
+ }
+
+ private final Ref[] list;
+
+ private final int cnt;
+
+ RefList(Ref[] list, int cnt) {
+ this.list = list;
+ this.cnt = cnt;
+ }
+
+ /**
+ * Initialize this list to use the same backing array as another list.
+ *
+ * @param src
+ * the source list.
+ */
+ protected RefList(RefList<T> src) {
+ this.list = src.list;
+ this.cnt = src.cnt;
+ }
+
+ public Iterator<Ref> iterator() {
+ return new Iterator<Ref>() {
+ private int idx;
+
+ public boolean hasNext() {
+ return idx < cnt;
+ }
+
+ public Ref next() {
+ if (idx < cnt)
+ return list[idx++];
+ throw new NoSuchElementException();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ /** @return this cast as an immutable, standard {@link java.util.List}. */
+ public final List<Ref> asList() {
+ final List<Ref> r = Arrays.asList(list).subList(0, cnt);
+ return Collections.unmodifiableList(r);
+ }
+
+ /** @return number of items in this list. */
+ public final int size() {
+ return cnt;
+ }
+
+ /** @return true if the size of this list is 0. */
+ public final boolean isEmpty() {
+ return cnt == 0;
+ }
+
+ /**
+ * Locate an entry by name.
+ *
+ * @param name
+ * the name of the reference to find.
+ * @return the index the reference is at. If the entry is not present
+ * returns a negative value. The insertion position for the given
+ * name can be computed from {@code -(index + 1)}.
+ */
+ public final int find(String name) {
+ int high = cnt;
+ if (high == 0)
+ return -1;
+ int low = 0;
+ do {
+ final int mid = (low + high) >>> 1;
+ final int cmp = RefComparator.compareTo(list[mid], name);
+ if (cmp < 0)
+ low = mid + 1;
+ else if (cmp == 0)
+ return mid;
+ else
+ high = mid;
+ } while (low < high);
+ return -(low + 1);
+ }
+
+ /**
+ * Determine if a reference is present.
+ *
+ * @param name
+ * name of the reference to find.
+ * @return true if the reference is present; false if it is not.
+ */
+ public final boolean contains(String name) {
+ return 0 <= find(name);
+ }
+
+ /**
+ * Get a reference object by name.
+ *
+ * @param name
+ * the name of the reference.
+ * @return the reference object; null if it does not exist in this list.
+ */
+ public final T get(String name) {
+ int idx = find(name);
+ return 0 <= idx ? get(idx) : null;
+ }
+
+ /**
+ * Get the reference at a particular index.
+ *
+ * @param idx
+ * the index to obtain. Must be {@code 0 <= idx < size()}.
+ * @return the reference value, never null.
+ */
+ @SuppressWarnings("unchecked")
+ public final T get(int idx) {
+ return (T) list[idx];
+ }
+
+ /**
+ * Obtain a builder initialized with the first {@code n} elements.
+ * <p>
+ * Copies the first {@code n} elements from this list into a new builder,
+ * which can be used by the caller to add additional elements.
+ *
+ * @param n
+ * the number of elements to copy.
+ * @return a new builder with the first {@code n} elements already added.
+ */
+ public final Builder<T> copy(int n) {
+ Builder<T> r = new Builder<T>(Math.max(16, n));
+ r.addAll(list, 0, n);
+ return r;
+ }
+
+ /**
+ * Obtain a new copy of the list after changing one element.
+ * <p>
+ * This list instance is not affected by the replacement. Because this
+ * method copies the entire list, it runs in O(N) time.
+ *
+ * @param idx
+ * index of the element to change.
+ * @param ref
+ * the new value, must not be null.
+ * @return copy of this list, after replacing {@code idx} with {@code ref} .
+ */
+ public final RefList<T> set(int idx, T ref) {
+ Ref[] newList = new Ref[cnt];
+ System.arraycopy(list, 0, newList, 0, cnt);
+ newList[idx] = ref;
+ return new RefList<T>(newList, cnt);
+ }
+
+ /**
+ * Add an item at a specific index.
+ * <p>
+ * This list instance is not affected by the addition. Because this method
+ * copies the entire list, it runs in O(N) time.
+ *
+ * @param idx
+ * position to add the item at. If negative the method assumes it
+ * was a direct return value from {@link #find(String)} and will
+ * adjust it to the correct position.
+ * @param ref
+ * the new reference to insert.
+ * @return copy of this list, after making space for and adding {@code ref}.
+ */
+ public final RefList<T> add(int idx, T ref) {
+ if (idx < 0)
+ idx = -(idx + 1);
+
+ Ref[] newList = new Ref[cnt + 1];
+ if (0 < idx)
+ System.arraycopy(list, 0, newList, 0, idx);
+ newList[idx] = ref;
+ if (idx < cnt)
+ System.arraycopy(list, idx, newList, idx + 1, cnt - idx);
+ return new RefList<T>(newList, cnt + 1);
+ }
+
+ /**
+ * Remove an item at a specific index.
+ * <p>
+ * This list instance is not affected by the addition. Because this method
+ * copies the entire list, it runs in O(N) time.
+ *
+ * @param idx
+ * position to remove the item from.
+ * @return copy of this list, after making removing the item at {@code idx}.
+ */
+ public final RefList<T> remove(int idx) {
+ if (cnt == 1)
+ return emptyList();
+ Ref[] newList = new Ref[cnt - 1];
+ if (0 < idx)
+ System.arraycopy(list, 0, newList, 0, idx);
+ if (idx + 1 < cnt)
+ System.arraycopy(list, idx + 1, newList, idx, cnt - (idx + 1));
+ return new RefList<T>(newList, cnt - 1);
+ }
+
+ /**
+ * Store a reference, adding or replacing as necessary.
+ * <p>
+ * This list instance is not affected by the store. The correct position is
+ * determined, and the item is added if missing, or replaced if existing.
+ * Because this method copies the entire list, it runs in O(N + log N) time.
+ *
+ * @param ref
+ * the reference to store.
+ * @return copy of this list, after performing the addition or replacement.
+ */
+ public final RefList<T> put(T ref) {
+ int idx = find(ref.getName());
+ if (0 <= idx)
+ return set(idx, ref);
+ return add(idx, ref);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder r = new StringBuilder();
+ r.append('[');
+ if (cnt > 0) {
+ r.append(list[0]);
+ for (int i = 1; i < cnt; i++) {
+ r.append(", ");
+ r.append(list[i]);
+ }
+ }
+ r.append(']');
+ return r.toString();
+ }
+
+ /**
+ * Builder to facilitate fast construction of an immutable RefList.
+ *
+ * @param <T>
+ * type of reference being stored.
+ */
+ public static class Builder<T extends Ref> {
+ private Ref[] list;
+
+ private int size;
+
+ /** Create an empty list ready for items to be added. */
+ public Builder() {
+ this(16);
+ }
+
+ /**
+ * Create an empty list with at least the specified capacity.
+ *
+ * @param capacity
+ * the new capacity.
+ */
+ public Builder(int capacity) {
+ list = new Ref[capacity];
+ }
+
+ /** @return number of items in this builder's internal collection. */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Get the reference at a particular index.
+ *
+ * @param idx
+ * the index to obtain. Must be {@code 0 <= idx < size()}.
+ * @return the reference value, never null.
+ */
+ @SuppressWarnings("unchecked")
+ public T get(int idx) {
+ return (T) list[idx];
+ }
+
+ /**
+ * Remove an item at a specific index.
+ *
+ * @param idx
+ * position to remove the item from.
+ */
+ public void remove(int idx) {
+ System.arraycopy(list, idx + 1, list, idx, size - (idx + 1));
+ size--;
+ }
+
+ /**
+ * Add the reference to the end of the array.
+ * <p>
+ * References must be added in sort order, or the array must be sorted
+ * after additions are complete using {@link #sort()}.
+ *
+ * @param ref
+ */
+ public void add(T ref) {
+ if (list.length == size) {
+ Ref[] n = new Ref[size * 2];
+ System.arraycopy(list, 0, n, 0, size);
+ list = n;
+ }
+ list[size++] = ref;
+ }
+
+ /**
+ * Add all items from a source array.
+ * <p>
+ * References must be added in sort order, or the array must be sorted
+ * after additions are complete using {@link #sort()}.
+ *
+ * @param src
+ * the source array.
+ * @param off
+ * position within {@code src} to start copying from.
+ * @param cnt
+ * number of items to copy from {@code src}.
+ */
+ public void addAll(Ref[] src, int off, int cnt) {
+ if (list.length < size + cnt) {
+ Ref[] n = new Ref[Math.max(size * 2, size + cnt)];
+ System.arraycopy(list, 0, n, 0, size);
+ list = n;
+ }
+ System.arraycopy(src, off, list, size, cnt);
+ size += cnt;
+ }
+
+ /**
+ * Replace a single existing element.
+ *
+ * @param idx
+ * index, must have already been added previously.
+ * @param ref
+ * the new reference.
+ */
+ public void set(int idx, T ref) {
+ list[idx] = ref;
+ }
+
+ /** Sort the list's backing array in-place. */
+ public void sort() {
+ Arrays.sort(list, 0, size, RefComparator.INSTANCE);
+ }
+
+ /** @return an unmodifiable list using this collection's backing array. */
+ public RefList<T> toRefList() {
+ return new RefList<T>(list, size);
+ }
+
+ @Override
+ public String toString() {
+ return toRefList().toString();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
new file mode 100644
index 0000000000..8a21cff73b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2010, 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 java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefComparator;
+
+/**
+ * Specialized Map to present a {@code RefDatabase} namespace.
+ * <p>
+ * Although not declared as a {@link java.util.SortedMap}, iterators from this
+ * map's projections always return references in {@link RefComparator} ordering.
+ * The map's internal representation is a sorted array of {@link Ref} objects,
+ * which means lookup and replacement is O(log N), while insertion and removal
+ * can be as expensive as O(N + log N) while the list expands or contracts.
+ * Since this is not a general map implementation, all entries must be keyed by
+ * the reference name.
+ * <p>
+ * This class is really intended as a helper for {@code RefDatabase}, which
+ * needs to perform a merge-join of three sorted {@link RefList}s in order to
+ * present the unified namespace of the packed-refs file, the loose refs/
+ * directory tree, and the resolved form of any symbolic references.
+ */
+public class RefMap extends AbstractMap<String, Ref> {
+ /**
+ * Prefix denoting the reference subspace this map contains.
+ * <p>
+ * All reference names in this map must start with this prefix. If the
+ * prefix is not the empty string, it must end with a '/'.
+ */
+ private final String prefix;
+
+ /** Immutable collection of the packed references at construction time. */
+ private RefList<Ref> packed;
+
+ /**
+ * Immutable collection of the loose references at construction time.
+ * <p>
+ * If an entry appears here and in {@link #packed}, this entry must take
+ * precedence, as its more current. Symbolic references in this collection
+ * are typically unresolved, so they only tell us who their target is, but
+ * not the current value of the target.
+ */
+ private RefList<Ref> loose;
+
+ /**
+ * Immutable collection of resolved symbolic references.
+ * <p>
+ * This collection contains only the symbolic references we were able to
+ * resolve at map construction time. Other loose references must be read
+ * from {@link #loose}. Every entry in this list must be matched by an entry
+ * in {@code loose}, otherwise it might be omitted by the map.
+ */
+ private RefList<Ref> resolved;
+
+ private int size;
+
+ private boolean sizeIsValid;
+
+ private Set<Entry<String, Ref>> entrySet;
+
+ /** Construct an empty map with a small initial capacity. */
+ public RefMap() {
+ prefix = "";
+ packed = RefList.emptyList();
+ loose = RefList.emptyList();
+ resolved = RefList.emptyList();
+ }
+
+ /**
+ * Construct a map to merge 3 collections together.
+ *
+ * @param prefix
+ * prefix used to slice the lists down. Only references whose
+ * names start with this prefix will appear to reside in the map.
+ * Must not be null, use {@code ""} (the empty string) to select
+ * all list items.
+ * @param packed
+ * items from the packed reference list, this is the last list
+ * searched.
+ * @param loose
+ * items from the loose reference list, this list overrides
+ * {@code packed} if a name appears in both.
+ * @param resolved
+ * resolved symbolic references. This list overrides the prior
+ * list {@code loose}, if an item appears in both. Items in this
+ * list <b>must</b> also appear in {@code loose}.
+ */
+ public RefMap(String prefix, RefList<Ref> packed, RefList<Ref> loose,
+ RefList<Ref> resolved) {
+ this.prefix = prefix;
+ this.packed = packed;
+ this.loose = loose;
+ this.resolved = resolved;
+ }
+
+ @Override
+ public boolean containsKey(Object name) {
+ return get(name) != null;
+ }
+
+ @Override
+ public Ref get(Object key) {
+ String name = toRefName((String) key);
+ Ref ref = resolved.get(name);
+ if (ref == null)
+ ref = loose.get(name);
+ if (ref == null)
+ ref = packed.get(name);
+ return ref;
+ }
+
+ @Override
+ public Ref put(final String keyName, Ref value) {
+ String name = toRefName(keyName);
+
+ if (!name.equals(value.getName()))
+ throw new IllegalArgumentException();
+
+ if (!resolved.isEmpty()) {
+ // Collapse the resolved list into the loose list so we
+ // can discard it and stop joining the two together.
+ for (Ref ref : resolved)
+ loose = loose.put(ref);
+ resolved = RefList.emptyList();
+ }
+
+ int idx = loose.find(name);
+ if (0 <= idx) {
+ Ref prior = loose.get(name);
+ loose = loose.set(idx, value);
+ return prior;
+ } else {
+ Ref prior = get(keyName);
+ loose = loose.add(idx, value);
+ sizeIsValid = false;
+ return prior;
+ }
+ }
+
+ @Override
+ public Ref remove(Object key) {
+ String name = toRefName((String) key);
+ Ref res = null;
+ int idx;
+ if (0 <= (idx = packed.find(name))) {
+ res = packed.get(name);
+ packed = packed.remove(idx);
+ sizeIsValid = false;
+ }
+ if (0 <= (idx = loose.find(name))) {
+ res = loose.get(name);
+ loose = loose.remove(idx);
+ sizeIsValid = false;
+ }
+ if (0 <= (idx = resolved.find(name))) {
+ res = resolved.get(name);
+ resolved = resolved.remove(idx);
+ sizeIsValid = false;
+ }
+ return res;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return entrySet().isEmpty();
+ }
+
+ @Override
+ public Set<Entry<String, Ref>> entrySet() {
+ if (entrySet == null) {
+ entrySet = new AbstractSet<Entry<String, Ref>>() {
+ @Override
+ public Iterator<Entry<String, Ref>> iterator() {
+ return new SetIterator();
+ }
+
+ @Override
+ public int size() {
+ if (!sizeIsValid) {
+ size = 0;
+ Iterator<?> i = entrySet().iterator();
+ for (; i.hasNext(); i.next())
+ size++;
+ sizeIsValid = true;
+ }
+ return size;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ if (sizeIsValid)
+ return 0 == size;
+ return !iterator().hasNext();
+ }
+
+ @Override
+ public void clear() {
+ packed = RefList.emptyList();
+ loose = RefList.emptyList();
+ resolved = RefList.emptyList();
+ size = 0;
+ sizeIsValid = true;
+ }
+ };
+ }
+ return entrySet;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder r = new StringBuilder();
+ boolean first = true;
+ r.append('[');
+ for (Ref ref : values()) {
+ if (first)
+ first = false;
+ else
+ r.append(", ");
+ r.append(ref);
+ }
+ r.append(']');
+ return r.toString();
+ }
+
+ private String toRefName(String name) {
+ if (0 < prefix.length())
+ name = prefix + name;
+ return name;
+ }
+
+ private String toMapKey(Ref ref) {
+ String name = ref.getName();
+ if (0 < prefix.length())
+ name = name.substring(prefix.length());
+ return name;
+ }
+
+ private class SetIterator implements Iterator<Entry<String, Ref>> {
+ private int packedIdx;
+
+ private int looseIdx;
+
+ private int resolvedIdx;
+
+ private Entry<String, Ref> next;
+
+ SetIterator() {
+ if (0 < prefix.length()) {
+ packedIdx = -(packed.find(prefix) + 1);
+ looseIdx = -(loose.find(prefix) + 1);
+ resolvedIdx = -(resolved.find(prefix) + 1);
+ }
+ }
+
+ public boolean hasNext() {
+ if (next == null)
+ next = peek();
+ return next != null;
+ }
+
+ public Entry<String, Ref> next() {
+ if (hasNext()) {
+ Entry<String, Ref> r = next;
+ next = peek();
+ return r;
+ }
+ throw new NoSuchElementException();
+ }
+
+ public Entry<String, Ref> peek() {
+ if (packedIdx < packed.size() && looseIdx < loose.size()) {
+ Ref p = packed.get(packedIdx);
+ Ref l = loose.get(looseIdx);
+ int cmp = RefComparator.compareTo(p, l);
+ if (cmp < 0) {
+ packedIdx++;
+ return toEntry(p);
+ }
+
+ if (cmp == 0)
+ packedIdx++;
+ looseIdx++;
+ return toEntry(resolveLoose(l));
+ }
+
+ if (looseIdx < loose.size())
+ return toEntry(resolveLoose(loose.get(looseIdx++)));
+ if (packedIdx < packed.size())
+ return toEntry(packed.get(packedIdx++));
+ return null;
+ }
+
+ private Ref resolveLoose(final Ref l) {
+ if (resolvedIdx < resolved.size()) {
+ Ref r = resolved.get(resolvedIdx);
+ int cmp = RefComparator.compareTo(l, r);
+ if (cmp == 0) {
+ resolvedIdx++;
+ return r;
+ } else if (cmp > 0) {
+ // WTF, we have a symbolic entry but no match
+ // in the loose collection. That's an error.
+ throw new IllegalStateException();
+ }
+ }
+ return l;
+ }
+
+ private Ent toEntry(Ref p) {
+ if (p.getName().startsWith(prefix))
+ return new Ent(p);
+ packedIdx = packed.size();
+ looseIdx = loose.size();
+ resolvedIdx = resolved.size();
+ return null;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private class Ent implements Entry<String, Ref> {
+ private Ref ref;
+
+ Ent(Ref ref) {
+ this.ref = ref;
+ }
+
+ public String getKey() {
+ return toMapKey(ref);
+ }
+
+ public Ref getValue() {
+ return ref;
+ }
+
+ public Ref setValue(Ref value) {
+ Ref prior = put(getKey(), value);
+ ref = value;
+ return prior;
+ }
+
+ @Override
+ public int hashCode() {
+ return getKey().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Map.Entry) {
+ final Object key = ((Map.Entry) obj).getKey();
+ final Object val = ((Map.Entry) obj).getValue();
+ if (key instanceof String && val instanceof Ref) {
+ final Ref r = (Ref) val;
+ if (r.getName().equals(ref.getName())) {
+ final ObjectId a = r.getObjectId();
+ final ObjectId b = ref.getObjectId();
+ if (a != null && b != null && AnyObjectId.equals(a, b))
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return ref.toString();
+ }
+ }
+}

Back to the top