diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java index 12ec2aae5..05af175cf 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LsRemoteCommandTest.java @@ -21,6 +21,8 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.util.SystemReader; import org.junit.Test; public class LsRemoteCommandTest extends RepositoryTestCase { @@ -106,6 +108,20 @@ public void testLsRemoteWithoutLocalRepository() throws Exception { assertEquals(2, refs.size()); } + @Test + public void testLsRemoteWithoutLocalRepositoryUrlInsteadOf() + throws Exception { + String uri = fileUri(); + StoredConfig userConfig = SystemReader.getInstance().getUserConfig(); + userConfig.load(); + userConfig.setString("url", uri, "insteadOf", "file:///foo"); + userConfig.save(); + Collection refs = Git.lsRemoteRepository().setRemote("file:///foo") + .setHeads(true).call(); + assertNotNull(refs); + assertEquals(2, refs.size()); + } + @Test public void testLsRemoteWithSymRefs() throws Exception { File directory = createTempDirectory("testRepository"); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java index 0c691062f..c3415581e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, 2020 Christoph Brill and others + * Copyright (C) 2011, 2022 Christoph Brill 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 @@ -9,6 +9,7 @@ */ package org.eclipse.jgit.api; +import java.io.IOException; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.ArrayList; @@ -20,8 +21,8 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.NotSupportedException; -import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; @@ -30,6 +31,8 @@ import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.UrlConfig; +import org.eclipse.jgit.util.SystemReader; /** * The ls-remote command @@ -153,7 +156,7 @@ private Map execute() throws GitAPIException, try (Transport transport = repo != null ? Transport.open(repo, remote) - : Transport.open(new URIish(remote))) { + : Transport.open(new URIish(translate(remote)))) { transport.setOptionUploadPack(uploadPack); configure(transport); Collection refSpecs = new ArrayList<>(1); @@ -185,11 +188,16 @@ private Map execute() throws GitAPIException, throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfLsRemoteCommand, e); - } catch (TransportException e) { + } catch (IOException | ConfigInvalidException e) { throw new org.eclipse.jgit.api.errors.TransportException( - e.getMessage(), - e); + e.getMessage(), e); } } + private String translate(String uri) + throws IOException, ConfigInvalidException { + UrlConfig urls = new UrlConfig( + SystemReader.getInstance().getUserConfig()); + return urls.replace(uri); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java index 2f3160bb8..c4e105ec4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java @@ -16,10 +16,7 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import org.eclipse.jgit.lib.Config; @@ -54,10 +51,6 @@ public class RemoteConfig implements Serializable { private static final String KEY_TIMEOUT = "timeout"; //$NON-NLS-1$ - private static final String KEY_INSTEADOF = "insteadof"; //$NON-NLS-1$ - - private static final String KEY_PUSHINSTEADOF = "pushinsteadof"; //$NON-NLS-1$ - private static final boolean DEFAULT_MIRROR = false; /** Default value for {@link #getUploadPack()} if not specified. */ @@ -135,10 +128,10 @@ public RemoteConfig(Config rc, String remoteName) String val; vlst = rc.getStringList(SECTION, name, KEY_URL); - Map insteadOf = getReplacements(rc, KEY_INSTEADOF); + UrlConfig urls = new UrlConfig(rc); uris = new ArrayList<>(vlst.length); for (String s : vlst) { - uris.add(new URIish(replaceUri(s, insteadOf))); + uris.add(new URIish(urls.replace(s))); } String[] plst = rc.getStringList(SECTION, name, KEY_PUSHURL); pushURIs = new ArrayList<>(plst.length); @@ -148,11 +141,9 @@ public RemoteConfig(Config rc, String remoteName) if (pushURIs.isEmpty()) { // Would default to the uris. If we have pushinsteadof, we must // supply rewritten push uris. - Map pushInsteadOf = getReplacements(rc, - KEY_PUSHINSTEADOF); - if (!pushInsteadOf.isEmpty()) { + if (urls.hasPushReplacements()) { for (String s : vlst) { - String replaced = replaceUri(s, pushInsteadOf); + String replaced = urls.replacePush(s); if (!s.equals(replaced)) { pushURIs.add(new URIish(replaced)); } @@ -248,39 +239,6 @@ private void unset(Config rc, String key) { rc.unset(SECTION, getName(), key); } - private Map getReplacements(final Config config, - final String keyName) { - final Map replacements = new HashMap<>(); - for (String url : config.getSubsections(KEY_URL)) - for (String insteadOf : config.getStringList(KEY_URL, url, keyName)) - replacements.put(insteadOf, url); - return replacements; - } - - private String replaceUri(final String uri, - final Map replacements) { - if (replacements.isEmpty()) { - return uri; - } - Entry match = null; - for (Entry replacement : replacements.entrySet()) { - // Ignore current entry if not longer than previous match - if (match != null - && match.getKey().length() > replacement.getKey() - .length()) { - continue; - } - if (!uri.startsWith(replacement.getKey())) { - continue; - } - match = replacement; - } - if (match != null) { - return match.getValue() + uri.substring(match.getKey().length()); - } - return uri; - } - /** * Get the local name this remote configuration is recognized as. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UrlConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UrlConfig.java new file mode 100644 index 000000000..574fcf806 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UrlConfig.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2022 Thomas Wolf 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 java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.jgit.lib.Config; + +/** + * Support for URL translations via git configs {@code url..insteadOf} and + * {@code url..pushInsteadOf}. + * + * @since 6.2 + */ +public class UrlConfig { + + private static final String KEY_INSTEADOF = "insteadof"; //$NON-NLS-1$ + + private static final String KEY_PUSHINSTEADOF = "pushinsteadof"; //$NON-NLS-1$ + + private static final String SECTION_URL = "url"; //$NON-NLS-1$ + + private final Config config; + + private Map insteadOf; + + private Map pushInsteadOf; + + /** + * Creates a new {@link UrlConfig} instance. + * + * @param config + * {@link Config} to read values from + */ + public UrlConfig(Config config) { + this.config = config; + } + + /** + * Performs replacements as defined by git config + * {@code url..insteadOf}. If there is no match, the input is returned + * unchanged. + * + * @param url + * to substitute + * @return the {@code url} with substitution applied + */ + public String replace(String url) { + if (insteadOf == null) { + insteadOf = load(KEY_INSTEADOF); + } + return replace(url, insteadOf); + } + + /** + * Tells whether there are push replacements. + * + * @return {@code true} if there are push replacements, {@code false} + * otherwise + */ + public boolean hasPushReplacements() { + if (pushInsteadOf == null) { + pushInsteadOf = load(KEY_PUSHINSTEADOF); + } + return !pushInsteadOf.isEmpty(); + } + + /** + * Performs replacements as defined by git config + * {@code url..pushInsteadOf}. If there is no match, the input is + * returned unchanged. + * + * @param url + * to substitute + * @return the {@code url} with substitution applied + */ + public String replacePush(String url) { + if (pushInsteadOf == null) { + pushInsteadOf = load(KEY_PUSHINSTEADOF); + } + return replace(url, pushInsteadOf); + } + + private Map load(String key) { + Map replacements = new HashMap<>(); + for (String url : config.getSubsections(SECTION_URL)) { + for (String prefix : config.getStringList(SECTION_URL, url, key)) { + replacements.put(prefix, url); + } + } + return replacements; + } + + private String replace(String uri, Map replacements) { + Entry match = null; + for (Entry replacement : replacements.entrySet()) { + // Ignore current entry if not longer than previous match + if (match != null && match.getKey().length() > replacement.getKey() + .length()) { + continue; + } + if (uri.startsWith(replacement.getKey())) { + match = replacement; + } + } + if (match != null) { + return match.getValue() + uri.substring(match.getKey().length()); + } + return uri; + } +}