Browse Source

Rewrite reference handling to be abstract and accurate

This commit actually does three major changes to the way references
are handled within JGit.  Unfortunately they were easier to do as
a single massive commit than to break them up into smaller units.

Disambiguate symbolic references:
---------------------------------

  Reporting a symbolic reference such as HEAD as though it were
  any other normal reference like refs/heads/master causes subtle
  programming errors.  We have been bitten by this error on several
  occasions, as have some downstream applications written by myself.

  Instead of reporting HEAD as a reference whose name differs from
  its "original name", report it as an actual SymbolicRef object
  that the application can test the type and examine the target of.

  With this change, Ref is now an abstract type with different
  subclasses for the different types.

  In the classical example of "HEAD" being a symbolic reference to
  branch "refs/heads/master", the Repository.getAllRefs() method
  will now return:

      Map<String, Ref> all = repository.getAllRefs();
      SymbolicRef HEAD = (SymbolicRef) all.get("HEAD");
      ObjectIdRef master = (ObjectIdRef) all.get("refs/heads/master");

      assertSame(master,               HEAD.getTarget());
      assertSame(master.getObjectId(), HEAD.getObjectId());

      assertEquals("HEAD",              HEAD.getName());
      assertEquals("refs/heads/master", master.getName());

  A nice side-effect of this change is the storage type of the
  symbolic reference is no longer ambiguous with the storge type
  of the underlying reference it targets.  In the above example,
  if master was only available in the packed-refs file, then the
  following is also true:

      assertSame(Ref.Storage.LOOSE,  HEAD.getStorage());
      assertSame(Ref.Storage.PACKED, master.getStorage());

  (Prior to this change we returned the ambiguous storage of
   LOOSE_PACKED for HEAD, which was confusing since it wasn't
   actually true on disk).

  Another nice side-effect of this change is all intermediate
  symbolic references are preserved, and are therefore visible
  to the application when they walk the target chain.  We can
  now correctly inspect chains of symbolic references.

  As a result of this change the Ref.getOrigName() method has been
  removed from the API.  Applications should identify a symbolic
  reference by testing for isSymbolic() and not by using an arcane
  string comparsion between properties.

Abstract the RefDatabase storage:
---------------------------------

  RefDatabase is now abstract, similar to ObjectDatabase, and a
  new concrete implementation called RefDirectory is used for the
  traditional on-disk storage layout.  In the future we plan to
  support additional implementations, such as a pure in-memory
  RefDatabase for unit testing purposes.

Optimize RefDirectory:
----------------------

  The implementation of the in-memory reference cache, reading, and
  update routines has been completely rewritten.  Much of the code
  was heavily borrowed or cribbed from the prior implementation,
  so copyright notices have been left intact as much as possible.

  The RefDirectory cache no longer confuses symbolic references
  with normal references.  This permits the cache to resolve the
  value of a symbolic reference as late as possible, ensuring it
  is always current, without needing to maintain reverse pointers.

  The cache is now 2 sorted RefLists, rather than 3 HashMaps.
  Using sorted lists allows the implementation to reduce the
  in-memory footprint when storing many refs.  Using specialized
  types for the elements allows the code to avoid additional map
  lookups for auxiliary stat information.

  To improve scan time during getRefs(), the lists are returned via
  a copy-on-write contract.  Most callers of getRefs() do not modify
  the returned collections, so the copy-on-write semantics improves
  access on repositories with a large number of packed references.

  Iterator traversals of the returned Map<String,Ref> are performed
  using a simple merge-join of the two cache lists, ensuring we can
  perform the entire traversal in linear time as a function of the
  number of references: O(PackedRefs + LooseRefs).

  Scans of the loose reference space to update the cache run in
  O(LooseRefs log LooseRefs) time, as the directory contents
  are sorted before being merged against the in-memory cache.
  Since the majority of stable references are kept packed, there
  typically are only a handful of reference names to be sorted,
  so the sorting cost should not be very high.

  Locking is reduced during getRefs() by taking advantage of the
  copy-on-write semantics of the improved cache data structure.
  This permits concurrent readers to pull back references without
  blocking each other.  If there is contention updating the cache
  during a scan, one or more updates are simply skipped and will
  get picked up again in a future scan.

  Writing to the $GIT_DIR/packed-refs during reference delete is
  now fully atomic.  The file is locked, reparsed fresh, and written
  back out if a change is necessary.  This avoids all race conditions
  with concurrent external updates of the packed-refs file.

  The RefLogWriter class has been fully folded into RefDirectory
  and is therefore deleted.  Maintaining the reference's log is
  the responsiblity of the database implementation, and not all
  implementations will use java.io for access.

  Future work still remains to be done to abstract the ReflogReader
  class away from local disk IO.

Change-Id: I26b9287c45a4b2d2be35ba2849daa316f5eec85d
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
tags/v0.7.0
Shawn O. Pearce 14 years ago
parent
commit
01b5392cdb
39 changed files with 3766 additions and 1241 deletions
  1. 1
    2
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
  2. 1
    1
      org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java
  3. 2
    1
      org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java
  4. 14
    2
      org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java
  5. 4
    2
      org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java
  6. 115
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java
  7. 1024
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java
  8. 26
    11
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
  9. 40
    38
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java
  10. 2
    2
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java
  11. 129
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java
  12. 22
    0
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java
  13. 16
    15
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java
  14. 14
    13
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java
  15. 2
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java
  16. 91
    1
      org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java
  17. 2
    1
      org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java
  18. 35
    1
      org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java
  19. 188
    0
      org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
  20. 44
    124
      org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
  21. 128
    459
      org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
  22. 1015
    0
      org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java
  23. 217
    0
      org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java
  24. 135
    0
      org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java
  25. 0
    158
      org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java
  26. 105
    100
      org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
  27. 97
    179
      org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
  28. 26
    7
      org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
  29. 83
    76
      org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
  30. 121
    0
      org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
  31. 5
    4
      org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
  32. 4
    3
      org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
  33. 3
    4
      org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
  34. 3
    3
      org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
  35. 6
    5
      org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
  36. 17
    12
      org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
  37. 11
    10
      org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
  38. 3
    2
      org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
  39. 15
    4
      org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java

+ 1
- 2
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java View File

@@ -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,7 +97,7 @@ 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());
return out.toString().getBytes(Constants.CHARACTER_ENCODING);

+ 1
- 1
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java View File

@@ -143,7 +143,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);

+ 2
- 1
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java View File

@@ -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(" ");
}

+ 14
- 2
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java View File

@@ -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');

+ 4
- 2
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java View File

@@ -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();

+ 115
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java View File

@@ -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());
}
}

+ 1024
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefDirectoryTest.java
File diff suppressed because it is too large
View File


+ 26
- 11
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java View File

@@ -75,7 +75,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");
@@ -87,7 +87,11 @@ public class RefTest extends SampleDataRepositoryTestCase {
public void testReadSymRefToPacked() throws IOException {
db.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 {
@@ -100,7 +104,10 @@ public class RefTest extends SampleDataRepositoryTestCase {

db.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 +136,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 +156,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());
}
}

+ 40
- 38
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java View File

@@ -260,10 +260,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 +308,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"));
@@ -337,7 +337,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 +414,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,7 +433,7 @@ 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");
@@ -440,11 +443,14 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
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 +581,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");
@@ -598,8 +604,8 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
db.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 +631,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");
@@ -657,7 +662,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
db.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 +696,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 +738,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,12 +750,6 @@ 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");
@@ -767,7 +760,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase {
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());

@@ -800,7 +793,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 +816,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);
}
}

+ 2
- 2
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogConfigTest.java View File

@@ -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

+ 129
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java View File

@@ -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());
}
}

+ 22
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java View File

@@ -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());
}
}

+ 16
- 15
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java View File

@@ -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);

+ 14
- 13
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java View File

@@ -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));
}

+ 2
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefListTest.java View File

@@ -49,6 +49,7 @@ 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 {
@@ -426,6 +427,6 @@ public class RefListTest extends TestCase {
}

private static Ref newRef(final String name) {
return new Ref(Ref.Storage.LOOSE, name, ID);
return new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID);
}
}

+ 91
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RefMapTest.java View File

@@ -50,7 +50,9 @@ 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
@@ -176,6 +178,49 @@ public class RefMapTest extends TestCase {
}
}

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);
@@ -297,6 +342,42 @@ public class RefMapTest extends TestCase {
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);
@@ -376,7 +457,16 @@ public class RefMapTest extends TestCase {
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 Ref(Ref.Storage.LOOSE, name, id);
return new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, id);
}
}

+ 2
- 1
org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java View File

@@ -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());

+ 35
- 1
org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java View File

@@ -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);
}

/**
@@ -334,6 +344,30 @@ public class LockFile {
needStatInformation = on;
}

/**
* 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>

+ 188
- 0
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java View File

@@ -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();
}
}

+ 44
- 124
org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java View File

@@ -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();
}

+ 128
- 459
org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java View File

@@ -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,169 @@

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;
/**
* 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 //
};

RefDatabase(final Repository r) {
db = r;
gitDir = db.getDirectory();
refsDir = FS.resolve(gitDir, "refs");
packedRefsFile = FS.resolve(gitDir, Constants.PACKED_REFS);
clearCache();
}
/**
* 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;

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;
}
/** Magic value for {@link #getRefs(String)} to return all references. */
public static final String ALL = "";//$NON-NLS-1$

Repository getRepository() {
return db;
}
/**
* Initialize a new reference database at this location.
*
* @throws IOException
* the database could not be created.
*/
public abstract void create() throws IOException;

void create() {
refsDir.mkdir();
new File(refsDir, "heads").mkdir();
new File(refsDir, "tags").mkdir();
}
/** Close any resources held by this database. */
public abstract void close();

ObjectId idOf(final String name) throws IOException {
refreshPackedRefs();
final Ref r = readRefBasic(name, 0);
return r != null ? r.getObjectId() : null;
}
/**
* 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
* proposed name.
* @return true if the name overlaps with an existing reference; false if
* using this name right now would be safe.
* @throws IOException
* the database could not be read to check for conflicts.
*/
public abstract boolean isNameConflicting(String name) throws IOException;

/**
* Create a command to update, create or delete a ref in this repository.
* Create a symbolic reference from one name to another.
*
* @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.
* the name of the reference. Should be {@link Constants#HEAD} or
* starting with {@link Constants#R_REFS}.
* @param target
* the target of the reference.
* @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 could not be created or overwritten.
*/
RefUpdate newUpdate(final String name) throws IOException {
return newUpdate(name, false);
}
public abstract void link(String name, String target) 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;
}

+ 1015
- 0
org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectory.java
File diff suppressed because it is too large
View File


+ 217
- 0
org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryRename.java View File

@@ -0,0 +1,217 @@
/*
* 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 {
refdb.link(Constants.HEAD, target.getName());
return true;
} catch (IOException e) {
return false;
}
}
}

+ 135
- 0
org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDirectoryUpdate.java View File

@@ -0,0 +1,135 @@
/*
* 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 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() throws IOException {
Ref dst = getRef().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);
}
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;
}
}

+ 0
- 158
org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java View File

@@ -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);
}
}

+ 105
- 100
org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java View File

@@ -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;
}
}

+ 97
- 179
org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java View File

@@ -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,52 @@ 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)}.
*
* @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() 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;

/**
* Get the name of the ref this update will operate on.
@@ -193,16 +214,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 +312,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
@@ -339,6 +361,16 @@ public class RefUpdate {
return oldValue;
}

/**
* 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>
@@ -378,7 +410,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 +418,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 +436,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 +456,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,14 +476,23 @@ 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;
@@ -453,17 +501,14 @@ public class RefUpdate {

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())
return Result.LOCK_FAILURE;
if (expValue != null) {
final ObjectId o;
o = oldValue != null ? oldValue : ObjectId.zeroId();
@@ -471,39 +516,24 @@ 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();
}
}

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;
unlock();
}
return false;
}

private static RevObject safeParse(final RevWalk rw, final AnyObjectId id)
@@ -520,123 +550,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;
}
}

+ 26
- 7
org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java View File

@@ -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
@@ -70,6 +74,22 @@ public abstract class RefWriter {
this.refs = RefComparator.sort(refs);
}

/**
* @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>
@@ -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');
}


+ 83
- 76
org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java View File

@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
* Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2006-2009, 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);
@@ -310,6 +308,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 +594,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 +865,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 +878,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();
}
}

/**
@@ -911,53 +916,48 @@ public class Repository {
}

/**
* @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 +971,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 +1012,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 +1029,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 +1048,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 +1125,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 +1243,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 +1305,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;
}
}

+ 121
- 0
org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java View File

@@ -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();
}
}

+ 5
- 4
org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java View File

@@ -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);
}

+ 4
- 3
org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java View File

@@ -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);
}

+ 3
- 4
org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java View File

@@ -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()))
if (!head.isSymbolic())
adv.advertiseHave(head.getObjectId());
adv.includeAdditionalHaves();
if (adv.isEmpty())

+ 3
- 3
org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java View File

@@ -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
@@ -170,9 +170,9 @@ public abstract class RefAdvertiser {
for (final Ref r : RefComparator.sort(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() + "^{}");
}
}
}

+ 6
- 5
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java View File

@@ -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;
}

+ 17
- 12
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java View File

@@ -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);

+ 11
- 10
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java View File

@@ -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;
}

+ 3
- 2
org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java View File

@@ -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);

+ 15
- 4
org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java View File

@@ -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);
}
}

Loading…
Cancel
Save