Преглед изворни кода

Add support for reading symrefs from pack capabilities

A SymbolicRef is added to the advertised refs for any symref in
capabilities whose target is an advertised ref; this may replace an
existing entry, such as HEAD.

When cloning, if any advertised HEAD is symbolic then use the target
rather than looking for an advertised ref with a matching objectId.

Add --symref option to LsRemote command.

Bug: 514052
Change-Id: Idfb48e6f6e8dcfe57a6896883fe6d84d533aa9d0
Signed-off-by: Lee Worrall <worrall.la@gmail.com>
tags/v5.10.0.202012021225-rc1
Lee Worrall пре 3 година
родитељ
комит
9ebbfe93bb

+ 19
- 1
org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/LsRemoteTest.java Прегледај датотеку

@@ -33,7 +33,7 @@ public class LsRemoteTest extends CLIRepositoryTestCase {
git.add().addFilepattern("Test.txt").call();
git.commit().setMessage("Initial commit").call();

// create a master branch and switch to it
// create a test branch and switch to it
git.branchCreate().setName("test").call();
RefUpdate rup = db.updateRef(Constants.HEAD);
rup.link("refs/heads/test");
@@ -104,4 +104,22 @@ public class LsRemoteTest extends CLIRepositoryTestCase {
"" }, result.toArray());
}

@Test
public void testLsRemoteSymRefs() throws Exception {
final List<String> result = CLIGitCommand.execute(
"git ls-remote --symref " + shellQuote(db.getDirectory()), db);
assertArrayEquals(new String[] {
"ref: refs/heads/test HEAD",
"d0b1ef2b3dea02bb2ca824445c04e6def012c32c HEAD",
"d0b1ef2b3dea02bb2ca824445c04e6def012c32c refs/heads/master",
"d0b1ef2b3dea02bb2ca824445c04e6def012c32c refs/heads/test",
"efc02078d83a5226986ae917323acec7e1e8b7cb refs/tags/tag1",
"d0b1ef2b3dea02bb2ca824445c04e6def012c32c refs/tags/tag1^{}",
"4e4b837e0fd4ba83c003678b03592dc1509a4115 refs/tags/tag2",
"d0b1ef2b3dea02bb2ca824445c04e6def012c32c refs/tags/tag2^{}",
"489384bf8ace47522fe32093d2ceb85b65a6cbb1 refs/tags/tag3",
"d0b1ef2b3dea02bb2ca824445c04e6def012c32c refs/tags/tag3^{}",
"" }, result.toArray());
}

}

+ 1
- 0
org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties Прегледај датотеку

@@ -256,6 +256,7 @@ usage_LsFiles=Show information about files in the index and the working tree
usage_LsRemote=List references in a remote repository
usage_lsRemoteHeads=Show only refs starting with refs/heads
usage_lsRemoteTags=Show only refs starting with refs/tags
usage_lsRemoteSymref=In addition to the object pointed at, show the underlying ref pointed at when showing a symbolic ref.
usage_LsTree=List the contents of a tree object
usage_MakeCacheTree=Show the current cache tree structure
usage_Match=Only consider tags matching the given glob(7) pattern or patterns, excluding the "refs/tags/" prefix.

+ 15
- 0
org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java Прегледај датотеку

@@ -34,6 +34,9 @@ class LsRemote extends TextBuiltin {
@Option(name = "--timeout", metaVar = "metaVar_service", usage = "usage_abortConnectionIfNoActivity")
int timeout = -1;

@Option(name = "--symref", usage = "usage_lsRemoteSymref")
private boolean symref;

@Argument(index = 0, metaVar = "metaVar_uriish", required = true)
private String remote;

@@ -47,6 +50,9 @@ class LsRemote extends TextBuiltin {
try {
refs.addAll(command.call());
for (Ref r : refs) {
if (symref && r.isSymbolic()) {
show(r.getTarget(), r.getName());
}
show(r.getObjectId(), r.getName());
if (r.getPeeledObjectId() != null) {
show(r.getPeeledObjectId(), r.getName() + "^{}"); //$NON-NLS-1$
@@ -70,4 +76,13 @@ class LsRemote extends TextBuiltin {
outw.print(name);
outw.println();
}

private void show(Ref ref, String name)
throws IOException {
outw.print("ref: ");
outw.print(ref.getName());
outw.print('\t');
outw.print(name);
outw.println();
}
}

+ 36
- 25
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java Прегледај датотеку

@@ -92,7 +92,6 @@ public class CloneCommandTest extends RepositoryTestCase {
command.setURI(fileUri());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
ObjectId id = git2.getRepository().resolve("tag-for-blob");
assertNotNull(id);
assertEquals(git2.getRepository().getFullBranch(), "refs/heads/test");
@@ -277,8 +276,7 @@ public class CloneCommandTest extends RepositoryTestCase {
Git git2 = command.call();
addRepoToClose(git2.getRepository());

assertNotNull(git2);
assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
assertEquals("refs/heads/master", git2.getRepository().getFullBranch());
assertEquals(
"refs/heads/master, refs/remotes/origin/master, refs/remotes/origin/test",
allRefNames(git2.branchList().setListMode(ListMode.ALL).call()));
@@ -293,7 +291,6 @@ public class CloneCommandTest extends RepositoryTestCase {
git2 = command.call();
addRepoToClose(git2.getRepository());

assertNotNull(git2);
assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
assertEquals("refs/remotes/origin/master, refs/remotes/origin/test",
allRefNames(git2.branchList().setListMode(ListMode.ALL).call()));
@@ -308,8 +305,7 @@ public class CloneCommandTest extends RepositoryTestCase {
git2 = command.call();
addRepoToClose(git2.getRepository());

assertNotNull(git2);
assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
assertEquals("refs/heads/master", git2.getRepository().getFullBranch());
assertEquals("refs/heads/master, refs/heads/test", allRefNames(git2
.branchList().setListMode(ListMode.ALL).call()));
}
@@ -324,7 +320,6 @@ public class CloneCommandTest extends RepositoryTestCase {
Git git2 = command.call();
addRepoToClose(git2.getRepository());

assertNotNull(git2);
assertEquals("refs/heads/test", git2.getRepository().getFullBranch());
}

@@ -338,7 +333,6 @@ public class CloneCommandTest extends RepositoryTestCase {
Git git2 = command.call();
addRepoToClose(git2.getRepository());

assertNotNull(git2);
ObjectId taggedCommit = db.resolve("tag-initial^{commit}");
assertEquals(taggedCommit.name(), git2
.getRepository().getFullBranch());
@@ -355,10 +349,9 @@ public class CloneCommandTest extends RepositoryTestCase {
command.setURI(fileUri());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
assertNull(git2.getRepository().resolve("tag-for-blob"));
assertNotNull(git2.getRepository().resolve("tag-initial"));
assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
assertEquals("refs/heads/master", git2.getRepository().getFullBranch());
assertEquals("refs/remotes/origin/master", allRefNames(git2
.branchList().setListMode(ListMode.REMOTE).call()));
RemoteConfig cfg = new RemoteConfig(git2.getRepository().getConfig(),
@@ -383,10 +376,9 @@ public class CloneCommandTest extends RepositoryTestCase {
command.setBare(true);
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
assertNull(git2.getRepository().resolve("tag-for-blob"));
assertNotNull(git2.getRepository().resolve("tag-initial"));
assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
assertEquals("refs/heads/master", git2.getRepository().getFullBranch());
assertEquals("refs/heads/master", allRefNames(git2.branchList()
.setListMode(ListMode.ALL).call()));
RemoteConfig cfg = new RemoteConfig(git2.getRepository().getConfig(),
@@ -409,11 +401,10 @@ public class CloneCommandTest extends RepositoryTestCase {
command.setURI(fileUri());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
assertTrue(git2.getRepository().isBare());
assertNotNull(git2.getRepository().resolve("tag-for-blob"));
assertNotNull(git2.getRepository().resolve("tag-initial"));
assertEquals(git2.getRepository().getFullBranch(), "refs/heads/master");
assertEquals("refs/heads/master", git2.getRepository().getFullBranch());
assertEquals("refs/heads/master, refs/heads/test", allRefNames(
git2.branchList().setListMode(ListMode.ALL).call()));
assertNotNull(git2.getRepository().exactRef("refs/meta/foo/bar"));
@@ -436,7 +427,6 @@ public class CloneCommandTest extends RepositoryTestCase {
command.setURI(fileUri());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
assertNull(git2.getRepository().resolve("tag-for-blob"));
assertNull(git2.getRepository().resolve("refs/heads/master"));
assertNotNull(git2.getRepository().resolve("tag-initial"));
@@ -464,8 +454,7 @@ public class CloneCommandTest extends RepositoryTestCase {
command.setURI(fileUri());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
assertEquals(git2.getRepository().getFullBranch(), "refs/heads/test");
assertEquals("refs/heads/test", git2.getRepository().getFullBranch());
// Expect both remote branches to exist; setCloneAllBranches(true)
// should override any setBranchesToClone().
assertNotNull(
@@ -492,8 +481,7 @@ public class CloneCommandTest extends RepositoryTestCase {
command.setURI(fileUri());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
assertEquals(git2.getRepository().getFullBranch(), "refs/heads/test");
assertEquals("refs/heads/test", git2.getRepository().getFullBranch());
// Expect only the test branch; allBranches was re-set to false
assertNull(git2.getRepository().resolve("refs/remotes/origin/master"));
assertNotNull(git2.getRepository().resolve("refs/remotes/origin/test"));
@@ -525,7 +513,6 @@ public class CloneCommandTest extends RepositoryTestCase {
command.setURI(fileUri());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
// clone again
command = Git.cloneRepository();
command.setDirectory(directory);
@@ -551,7 +538,6 @@ public class CloneCommandTest extends RepositoryTestCase {
clone.setURI(fileUri());
Git git2 = clone.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);

assertEquals(Constants.MASTER, git2.getRepository().getBranch());
}
@@ -595,7 +581,6 @@ public class CloneCommandTest extends RepositoryTestCase {
clone.setURI(fileUri());
Git git2 = clone.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);

assertEquals(Constants.MASTER, git2.getRepository().getBranch());
assertTrue(new File(git2.getRepository().getWorkTree(), path
@@ -683,7 +668,6 @@ public class CloneCommandTest extends RepositoryTestCase {
clone.setURI(git.getRepository().getDirectory().toURI().toString());
Git git2 = clone.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);

assertEquals(Constants.MASTER, git2.getRepository().getBranch());
assertTrue(new File(git2.getRepository().getWorkTree(), path
@@ -813,7 +797,6 @@ public class CloneCommandTest extends RepositoryTestCase {
command.setNoTags();
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
assertNotNull(git2.getRepository().resolve("refs/heads/test"));
assertNull(git2.getRepository().resolve("tag-initial"));
assertNull(git2.getRepository().resolve("tag-for-blob"));
@@ -833,13 +816,41 @@ public class CloneCommandTest extends RepositoryTestCase {
command.setTagOption(TagOpt.FETCH_TAGS);
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertNotNull(git2);
assertNull(git2.getRepository().resolve("refs/heads/test"));
assertNotNull(git2.getRepository().resolve("tag-initial"));
assertNotNull(git2.getRepository().resolve("tag-for-blob"));
assertTagOption(git2.getRepository(), TagOpt.FETCH_TAGS);
}

@Test
public void testCloneWithHeadSymRefIsMasterCopy() throws IOException, GitAPIException {
// create a branch with the same head as master and switch to it
git.checkout().setStartPoint("master").setCreateBranch(true).setName("master-copy").call();

// when we clone the HEAD symref->master-copy means we start on master-copy and not master
File directory = createTempDirectory("testCloneRepositorySymRef_master-copy");
CloneCommand command = Git.cloneRepository();
command.setDirectory(directory);
command.setURI(fileUri());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertEquals("refs/heads/master-copy", git2.getRepository().getFullBranch());
}

@Test
public void testCloneWithHeadSymRefIsNonMasterCopy() throws IOException, GitAPIException {
// create a branch with the same head as test and switch to it
git.checkout().setStartPoint("test").setCreateBranch(true).setName("test-copy").call();

File directory = createTempDirectory("testCloneRepositorySymRef_test-copy");
CloneCommand command = Git.cloneRepository();
command.setDirectory(directory);
command.setURI(fileUri());
Git git2 = command.call();
addRepoToClose(git2.getRepository());
assertEquals("refs/heads/test-copy", git2.getRepository().getFullBranch());
}

private void assertTagOption(Repository repo, TagOpt expectedTagOption)
throws URISyntaxException {
RemoteConfig remoteConfig = new RemoteConfig(

+ 25
- 1
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java Прегледај датотеку

@@ -11,9 +11,11 @@ package org.eclipse.jgit.api;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.util.Collection;
import java.util.Optional;

import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants;
@@ -34,7 +36,7 @@ public class LsRemoteCommandTest extends RepositoryTestCase {
git.add().addFilepattern("Test.txt").call();
git.commit().setMessage("Initial commit").call();

// create a master branch and switch to it
// create a test branch and switch to it
git.branchCreate().setName("test").call();
RefUpdate rup = db.updateRef(Constants.HEAD);
rup.link("refs/heads/test");
@@ -104,6 +106,28 @@ public class LsRemoteCommandTest extends RepositoryTestCase {
assertEquals(2, refs.size());
}

@Test
public void testLsRemoteWithSymRefs() throws Exception {
File directory = createTempDirectory("testRepository");
CloneCommand command = Git.cloneRepository();
command.setDirectory(directory);
command.setURI(fileUri());
command.setCloneAllBranches(true);
Git git2 = command.call();
addRepoToClose(git2.getRepository());


LsRemoteCommand lsRemoteCommand = git2.lsRemote();
Collection<Ref> refs = lsRemoteCommand.call();
assertNotNull(refs);
assertEquals(6, refs.size());

Optional<Ref> headRef = refs.stream().filter(ref -> ref.getName().equals(Constants.HEAD)).findFirst();
assertTrue("expected a HEAD Ref", headRef.isPresent());
assertTrue("expected HEAD Ref to be a Symbolic", headRef.get().isSymbolic());
assertEquals("refs/heads/test", headRef.get().getTarget().getName());
}

private String fileUri() {
return "file://" + git.getRepository().getWorkTree().getAbsolutePath();
}

+ 233
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BasePackConnectionTest.java Прегледај датотеку

@@ -0,0 +1,233 @@
/*
* Copyright (C) 2020, Lee Worrall and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.transport;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.SymbolicRef;
import org.junit.Test;

public class BasePackConnectionTest {

@Test
public void testExtractSymRefsFromCapabilities() {
final Map<String, String> symRefs = BasePackConnection
.extractSymRefsFromCapabilities(
Arrays.asList("symref=HEAD:refs/heads/main",
"symref=refs/heads/sym:refs/heads/other"));

assertEquals(2, symRefs.size());
assertEquals("refs/heads/main", symRefs.get("HEAD"));
assertEquals("refs/heads/other", symRefs.get("refs/heads/sym"));
}

@Test
public void testUpdateWithSymRefsAdds() {
final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
"refs/heads/main", ObjectId.fromString(
"0000000000000000000000000000000000000001"));

final Map<String, Ref> refMap = new HashMap<>();
refMap.put(mainRef.getName(), mainRef);
refMap.put("refs/heads/other",
new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
ObjectId.fromString(
"0000000000000000000000000000000000000002")));

final Map<String, String> symRefs = new HashMap<>();
symRefs.put("HEAD", "refs/heads/main");

BasePackConnection.updateWithSymRefs(refMap, symRefs);

assertThat(refMap, hasKey("HEAD"));
final Ref headRef = refMap.get("HEAD");
assertThat(headRef, instanceOf(SymbolicRef.class));
final SymbolicRef headSymRef = (SymbolicRef) headRef;
assertEquals("HEAD", headSymRef.getName());
assertSame(mainRef, headSymRef.getTarget());
}

@Test
public void testUpdateWithSymRefsReplaces() {
final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
"refs/heads/main", ObjectId.fromString(
"0000000000000000000000000000000000000001"));

final Map<String, Ref> refMap = new HashMap<>();
refMap.put(mainRef.getName(), mainRef);
refMap.put("HEAD", new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "HEAD",
mainRef.getObjectId()));
refMap.put("refs/heads/other",
new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
ObjectId.fromString(
"0000000000000000000000000000000000000002")));

final Map<String, String> symRefs = new HashMap<>();
symRefs.put("HEAD", "refs/heads/main");

BasePackConnection.updateWithSymRefs(refMap, symRefs);

assertThat(refMap, hasKey("HEAD"));
final Ref headRef = refMap.get("HEAD");
assertThat(headRef, instanceOf(SymbolicRef.class));
final SymbolicRef headSymRef = (SymbolicRef) headRef;
assertEquals("HEAD", headSymRef.getName());
assertSame(mainRef, headSymRef.getTarget());
}

@Test
public void testUpdateWithSymRefsWithIndirectsAdds() {
final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
"refs/heads/main", ObjectId.fromString(
"0000000000000000000000000000000000000001"));

final Map<String, Ref> refMap = new HashMap<>();
refMap.put(mainRef.getName(), mainRef);
refMap.put("refs/heads/other",
new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
ObjectId.fromString(
"0000000000000000000000000000000000000002")));

final Map<String, String> symRefs = new LinkedHashMap<>(); // Ordered
symRefs.put("refs/heads/sym3", "refs/heads/sym2"); // Forward reference
symRefs.put("refs/heads/sym1", "refs/heads/main");
symRefs.put("refs/heads/sym2", "refs/heads/sym1"); // Backward reference

BasePackConnection.updateWithSymRefs(refMap, symRefs);

assertThat(refMap, hasKey("refs/heads/sym1"));
final Ref sym1Ref = refMap.get("refs/heads/sym1");
assertThat(sym1Ref, instanceOf(SymbolicRef.class));
final SymbolicRef sym1SymRef = (SymbolicRef) sym1Ref;
assertEquals("refs/heads/sym1", sym1SymRef.getName());
assertSame(mainRef, sym1SymRef.getTarget());

assertThat(refMap, hasKey("refs/heads/sym2"));
final Ref sym2Ref = refMap.get("refs/heads/sym2");
assertThat(sym2Ref, instanceOf(SymbolicRef.class));
final SymbolicRef sym2SymRef = (SymbolicRef) sym2Ref;
assertEquals("refs/heads/sym2", sym2SymRef.getName());
assertSame(sym1SymRef, sym2SymRef.getTarget());

assertThat(refMap, hasKey("refs/heads/sym3"));
final Ref sym3Ref = refMap.get("refs/heads/sym3");
assertThat(sym3Ref, instanceOf(SymbolicRef.class));
final SymbolicRef sym3SymRef = (SymbolicRef) sym3Ref;
assertEquals("refs/heads/sym3", sym3SymRef.getName());
assertSame(sym2SymRef, sym3SymRef.getTarget());
}

@Test
public void testUpdateWithSymRefsWithIndirectsReplaces() {
final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
"refs/heads/main", ObjectId.fromString(
"0000000000000000000000000000000000000001"));

final Map<String, Ref> refMap = new HashMap<>();
refMap.put(mainRef.getName(), mainRef);
refMap.put("refs/heads/sym1", new ObjectIdRef.Unpeeled(
Ref.Storage.LOOSE, "refs/heads/sym1", mainRef.getObjectId()));
refMap.put("refs/heads/sym2", new ObjectIdRef.Unpeeled(
Ref.Storage.LOOSE, "refs/heads/sym2", mainRef.getObjectId()));
refMap.put("refs/heads/sym3", new ObjectIdRef.Unpeeled(
Ref.Storage.LOOSE, "refs/heads/sym3", mainRef.getObjectId()));
refMap.put("refs/heads/other",
new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
ObjectId.fromString(
"0000000000000000000000000000000000000002")));

final Map<String, String> symRefs = new LinkedHashMap<>(); // Ordered
symRefs.put("refs/heads/sym3", "refs/heads/sym2"); // Forward reference
symRefs.put("refs/heads/sym1", "refs/heads/main");
symRefs.put("refs/heads/sym2", "refs/heads/sym1"); // Backward reference

BasePackConnection.updateWithSymRefs(refMap, symRefs);

assertThat(refMap, hasKey("refs/heads/sym1"));
final Ref sym1Ref = refMap.get("refs/heads/sym1");
assertThat(sym1Ref, instanceOf(SymbolicRef.class));
final SymbolicRef sym1SymRef = (SymbolicRef) sym1Ref;
assertEquals("refs/heads/sym1", sym1SymRef.getName());
assertSame(mainRef, sym1SymRef.getTarget());

assertThat(refMap, hasKey("refs/heads/sym2"));
final Ref sym2Ref = refMap.get("refs/heads/sym2");
assertThat(sym2Ref, instanceOf(SymbolicRef.class));
final SymbolicRef sym2SymRef = (SymbolicRef) sym2Ref;
assertEquals("refs/heads/sym2", sym2SymRef.getName());
assertSame(sym1SymRef, sym2SymRef.getTarget());

assertThat(refMap, hasKey("refs/heads/sym3"));
final Ref sym3Ref = refMap.get("refs/heads/sym3");
assertThat(sym3Ref, instanceOf(SymbolicRef.class));
final SymbolicRef sym3SymRef = (SymbolicRef) sym3Ref;
assertEquals("refs/heads/sym3", sym3SymRef.getName());
assertSame(sym2SymRef, sym3SymRef.getTarget());
}

@Test
public void testUpdateWithSymRefsIgnoresSelfReference() {
final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
"refs/heads/main", ObjectId.fromString(
"0000000000000000000000000000000000000001"));

final Map<String, Ref> refMap = new HashMap<>();
refMap.put(mainRef.getName(), mainRef);
refMap.put("refs/heads/other",
new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
ObjectId.fromString(
"0000000000000000000000000000000000000002")));

final Map<String, String> symRefs = new LinkedHashMap<>();
symRefs.put("refs/heads/sym1", "refs/heads/sym1");

BasePackConnection.updateWithSymRefs(refMap, symRefs);

assertEquals(2, refMap.size());
assertThat(refMap, not(hasKey("refs/heads/sym1")));
}

@Test
public void testUpdateWithSymRefsIgnoreCircularReference() {
final Ref mainRef = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE,
"refs/heads/main", ObjectId.fromString(
"0000000000000000000000000000000000000001"));

final Map<String, Ref> refMap = new HashMap<>();
refMap.put(mainRef.getName(), mainRef);
refMap.put("refs/heads/other",
new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/other",
ObjectId.fromString(
"0000000000000000000000000000000000000002")));

final Map<String, String> symRefs = new LinkedHashMap<>();
symRefs.put("refs/heads/sym2", "refs/heads/sym1");
symRefs.put("refs/heads/sym1", "refs/heads/sym2");

BasePackConnection.updateWithSymRefs(refMap, symRefs);

assertEquals(2, refMap.size());
assertThat(refMap, not(hasKey("refs/heads/sym1")));
assertThat(refMap, not(hasKey("refs/heads/sym2")));
}
}

+ 4
- 0
org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java Прегледај датотеку

@@ -413,6 +413,10 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
return null;
}

if (idHEAD != null && idHEAD.isSymbolic()) {
return idHEAD.getTarget();
}

Ref master = result.getAdvertisedRef(Constants.R_HEADS
+ Constants.MASTER);
ObjectId objectId = master != null ? master.getObjectId() : null;

+ 105
- 0
org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java Прегледај датотеку

@@ -21,8 +21,11 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.eclipse.jgit.errors.InvalidObjectIdException;
@@ -35,6 +38,7 @@ 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.lib.SymbolicRef;
import org.eclipse.jgit.util.io.InterruptTimer;
import org.eclipse.jgit.util.io.TimeoutInputStream;
import org.eclipse.jgit.util.io.TimeoutOutputStream;
@@ -49,6 +53,8 @@ import org.eclipse.jgit.util.io.TimeoutOutputStream;
*/
abstract class BasePackConnection extends BaseConnection {

protected static final String CAPABILITY_SYMREF_PREFIX = "symref="; //$NON-NLS-1$

/** The repository this transport fetches into, or pushes out of. */
protected final Repository local;

@@ -228,9 +234,108 @@ abstract class BasePackConnection extends BaseConnection {
throw duplicateAdvertisement(name);
}
}
updateWithSymRefs(avail, extractSymRefsFromCapabilities(remoteCapablities));
available(avail);
}

/**
* Finds values in the given capabilities of the form:
*
* <pre>
* symref=<em>source</em>:<em>target</em>
* </pre>
*
* And returns a Map of source->target entries.
*
* @param capabilities
* the capabilities lines
* @return a Map of the symref entries from capabilities
* @throws NullPointerException
* if capabilities, or any entry in it, is null
*/
static Map<String, String> extractSymRefsFromCapabilities(Collection<String> capabilities) {
final Map<String, String> symRefs = new LinkedHashMap<>();
for (String option : capabilities) {
if (option.startsWith(CAPABILITY_SYMREF_PREFIX)) {
String[] symRef = option
.substring(CAPABILITY_SYMREF_PREFIX.length())
.split(":", 2); //$NON-NLS-1$
if (symRef.length == 2) {
symRefs.put(symRef[0], symRef[1]);
}
}
}
return symRefs;
}

/**
* Updates the given refMap with {@link SymbolicRef}s defined by the given
* symRefs.
* <p>
* For each entry, symRef, in symRefs, whose value is a key in refMap, adds
* a new entry to refMap with that same key and value of a new
* {@link SymbolicRef} with source=symRef.key and
* target=refMap.get(symRef.value), then removes that entry from symRefs.
* <p>
* If refMap already contains an entry for symRef.key, it is replaced.
* </p>
* </p>
* <p>
* For example, given:
* </p>
*
* <pre>
* refMap.put("refs/heads/main", ref);
* symRefs.put("HEAD", "refs/heads/main");
* </pre>
*
* then:
*
* <pre>
* updateWithSymRefs(refMap, symRefs);
* </pre>
*
* has the <em>effect</em> of:
*
* <pre>
* refMap.put("HEAD",
* new SymbolicRef("HEAD", refMap.get(symRefs.remove("HEAD"))))
* </pre>
* <p>
* Any entry in symRefs whose value is not a key in refMap is ignored. Any
* circular symRefs are ignored.
* </p>
* <p>
* Upon completion, symRefs will contain only any unresolvable entries.
* </p>
*
* @param refMap
* a non-null, modifiable, Map to update, and the provider of
* symref targets.
* @param symRefs
* a non-null, modifiable, Map of symrefs.
* @throws NullPointerException
* if refMap or symRefs is null
*/
static void updateWithSymRefs(Map<String, Ref> refMap, Map<String, String> symRefs) {
boolean haveNewRefMapEntries = !refMap.isEmpty();
while (!symRefs.isEmpty() && haveNewRefMapEntries) {
haveNewRefMapEntries = false;
final Iterator<Map.Entry<String, String>> iterator = symRefs.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry<String, String> symRef = iterator.next();
if (!symRefs.containsKey(symRef.getValue())) { // defer forward reference
final Ref r = refMap.get(symRef.getValue());
if (r != null) {
refMap.put(symRef.getKey(), new SymbolicRef(symRef.getKey(), r));
haveNewRefMapEntries = true;
iterator.remove();
}
}
}
}
}

/**
* Create an exception to indicate problems finding a remote repository. The
* caller is expected to throw the returned exception.

Loading…
Откажи
Сачувај