From c127a1a6525dd1efe58dac5add7d927ba4b1c990 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Wed, 2 Mar 2022 20:47:49 +0100 Subject: [PATCH 1/2] Factor out URL replacement from RemoteConfig Add a new UrlConfig that encapsulates the basic URL replacement logic for git configs url..insteadOf and pushInsteadof. Change-Id: Iecc61d1c5e6089533552afa1d1e684ae72393b38 Signed-off-by: Thomas Wolf --- .../eclipse/jgit/transport/RemoteConfig.java | 50 +------- .../org/eclipse/jgit/transport/UrlConfig.java | 120 ++++++++++++++++++ 2 files changed, 124 insertions(+), 46 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/UrlConfig.java 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; + } +} From 2c1a8798ca05ab7b9f5935e0323066ad361ab0f1 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Fri, 4 Mar 2022 21:00:26 +0100 Subject: [PATCH 2/2] LsRemoteCommand: apply url.*.insteadOf If LsRemoteCommand is used without repository, the remote URI was not translated. If the git user config contains e.g. [url "ssh://git@github.com/"] insteadOf = https://github.com/ and LsRemoteCommand is called with an HTTPS GitHub repository URL, the command should actually rewrite this to an SSH URI and use the SSH transport. Actually this same problem may exist everywhere Transport is used with an URIish instead of with a remote name. However, doing this translation in Transport.open(URIish) and in Transport.open(Repository, URIish, String) if no remote name is given would change the behavior and might break assumptions made in existing clients. For instance, EGit's PushOperation assumes that the URI obtained from PushResult.getURI() was the same as was passed in to Transport.open(Repository, URIish). URIs obtained from a RemoteConfig have this translation applied transparently, and in Transport we cannot know for sure whether or not a URI has already been translated, if needed. So doing this in Transport might also lead to translating URIs twice. Therefore this commit does the translation in LsRemoteCommand, where we can be sure that it won't affect other use cases. If other cases besides LsRemoteCommand are found where such a URI translation is missing, it'll have to be done at higher levels, possibly even in client code directly. Bug: 544769 Change-Id: I5df54a925d30b55d98e21f37f2851fe79649b064 Signed-off-by: Thomas Wolf --- .../eclipse/jgit/api/LsRemoteCommandTest.java | 16 +++++++++++++++ .../org/eclipse/jgit/api/LsRemoteCommand.java | 20 +++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) 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); + } }