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>
This commit is contained in:
parent
dfa29458e7
commit
9ebbfe93bb
|
@ -33,7 +33,7 @@ public void setUp() throws Exception {
|
|||
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 void testLsRemoteHeadsTags() throws Exception {
|
|||
"" }, 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 @@ protected void run() {
|
|||
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 @@ private void show(AnyObjectId id, String name)
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,6 @@ public void testCloneRepository() throws IOException,
|
|||
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 void testCloneRepositoryWithBranch() throws IOException,
|
|||
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 void testCloneRepositoryWithBranch() throws IOException,
|
|||
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 void testCloneRepositoryWithBranch() throws IOException,
|
|||
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 void testCloneRepositoryWithBranchShortName() throws Exception {
|
|||
Git git2 = command.call();
|
||||
addRepoToClose(git2.getRepository());
|
||||
|
||||
assertNotNull(git2);
|
||||
assertEquals("refs/heads/test", git2.getRepository().getFullBranch());
|
||||
}
|
||||
|
||||
|
@ -338,7 +333,6 @@ public void testCloneRepositoryWithTagName() throws Exception {
|
|||
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 void testCloneRepositoryOnlyOneBranch() throws Exception {
|
|||
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 void testBareCloneRepositoryOnlyOneBranch() throws Exception {
|
|||
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 void testBareCloneRepositoryMirror() throws Exception {
|
|||
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 void testCloneRepositoryOnlyOneTag() throws Exception {
|
|||
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 void testCloneRepositoryAllBranchesTakesPreference()
|
|||
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 void testCloneRepositoryAllBranchesIndependent() throws Exception {
|
|||
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 void testCloneRepositoryWhenDestinationDirectoryExistsAndIsNotEmpty()
|
|||
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 void testCloneRepositoryWithMultipleHeadBranches() throws Exception {
|
|||
clone.setURI(fileUri());
|
||||
Git git2 = clone.call();
|
||||
addRepoToClose(git2.getRepository());
|
||||
assertNotNull(git2);
|
||||
|
||||
assertEquals(Constants.MASTER, git2.getRepository().getBranch());
|
||||
}
|
||||
|
@ -595,7 +581,6 @@ public void testCloneRepositoryWithSubmodules() throws Exception {
|
|||
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 void testCloneRepositoryWithNestedSubmodules() throws Exception {
|
|||
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 void testCloneNoTags() throws IOException, JGitInternalException,
|
|||
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 void testCloneFollowTags() throws IOException, JGitInternalException,
|
|||
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(
|
||||
|
|
|
@ -11,9 +11,11 @@
|
|||
|
||||
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 void setUp() throws Exception {
|
|||
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 void testLsRemoteWithoutLocalRepository() throws Exception {
|
|||
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();
|
||||
}
|
||||
|
|
|
@ -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")));
|
||||
}
|
||||
}
|
|
@ -413,6 +413,10 @@ private Ref findBranchToCheckout(FetchResult result) {
|
|||
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;
|
||||
|
|
|
@ -21,8 +21,11 @@
|
|||
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.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 @@
|
|||
*/
|
||||
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 @@ private void readAdvertisedRefsImpl() throws IOException {
|
|||
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…
Reference in New Issue