Skip to content

Conversation

@akash-manna-sky
Copy link
Contributor

@akash-manna-sky akash-manna-sky commented Jan 1, 2026

JGit fails 2nd and later https fetch with embedded username & password

Fixes #1657

Testing done

Submitter checklist

  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests that demonstrate the feature works or the issue is fixed

@github-actions github-actions bot added the tests Automated test addition or improvement label Jan 1, 2026
…extract and use credentials from URLs, and update tests to avoid SpotBugs warnings.
@akash-manna-sky akash-manna-sky marked this pull request as ready for review January 15, 2026 21:21
@akash-manna-sky akash-manna-sky requested a review from a team as a code owner January 15, 2026 21:21
@MarkEWaite MarkEWaite requested a review from Copilot January 15, 2026 21:34
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a bug where JGit fails on subsequent fetch operations when credentials are embedded in HTTPS URLs stored in git config (JENKINS-69507). The issue occurs when a repository URL with embedded credentials (e.g., https://user:pass@host/repo.git) is stored as a git remote, and subsequent fetch operations using the remote name fail to authenticate because JGit doesn't automatically extract and use the embedded credentials.

Changes:

  • Added logic to extract embedded credentials from URLs resolved from git config during fetch operations
  • Created an EmbeddedCredentials class to hold extracted username/password pairs
  • Enabled embedded credentials integration tests for JGit in addition to CLI git

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java Implements the core fix by adding credential extraction logic to the fetch operation and creating an EmbeddedCredentials class to store extracted credentials
src/test/java/org/jenkinsci/plugins/gitclient/JGitEmbeddedCredentialsTest.java Adds unit tests for credential extraction and URL handling scenarios
src/test/java/org/jenkinsci/plugins/gitclient/CredentialsTest.java Enables existing integration tests to run with JGit for embedded credentials scenarios

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* If the URL looks like a remote name (not a full URL), resolve it from git config first
*/
URIish urlForCredentials = url;
if (!url.isRemote() && !org.apache.commons.lang3.StringUtils.containsAny(url.toString(), ":@/\\")) {
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition on line 904 appears to have redundant or confusing logic. The check !url.isRemote() already determines if the URL is a remote name vs a full URL. The additional check !StringUtils.containsAny(url.toString(), ":@/\\") may be unnecessary since a remote name that passes !url.isRemote() would typically not contain these characters anyway. Consider simplifying this to just !url.isRemote() or add a comment explaining why both checks are needed.

Suggested change
if (!url.isRemote() && !org.apache.commons.lang3.StringUtils.containsAny(url.toString(), ":@/\\")) {
if (!url.isRemote()) {

Copilot uses AI. Check for mistakes.
Comment on lines +805 to +806

addCredentials(url.toString(), embeddedCredentials);
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding credentials on line 806, the URL key includes the embedded credentials (e.g., "https://user:pass@host/repo.git"). However, JGit's TransportHttp may query the credentials provider with a URL that has the credentials stripped out (e.g., "https://host/repo.git"). While the SmartCredentialsProvider has fallback logic to match by scheme/host/port (lines 132-149 in SmartCredentialsProvider.java), it would be more robust and efficient to also add the credentials using a URL without the embedded credentials as an additional key. This would avoid reliance on the fallback matching logic and improve performance.

Suggested change
addCredentials(url.toString(), embeddedCredentials);
// Add credentials keyed by the full URL (including embedded credentials)
addCredentials(url.toString(), embeddedCredentials);
// Also add credentials keyed by the URL without embedded credentials,
// since JGit's TransportHttp may query using a stripped URL.
try {
URIish urlWithoutCredentials = new URIish(url).setUser(null).setPass(null);
addCredentials(urlWithoutCredentials.toString(), embeddedCredentials);
} catch (URISyntaxException e) {
// Fall back to the original key only if stripping credentials fails
LOGGER.log(Level.FINE, "Failed to create URI without embedded credentials for " + url, e);
}

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +106
@Test
void testRemoteNameResolutionWithEmbeddedCredentials() throws Exception {
TaskListener listener = StreamTaskListener.fromStdout();
JGitAPIImpl gitClient = new JGitAPIImpl(tempDir, listener);

gitClient.init_().workspace(tempDir.getAbsolutePath()).execute();

String urlWithCreds = "https://testuser:[email protected]/repo.git";
gitClient.setRemoteUrl("origin", urlWithCreds);

String storedUrl = gitClient.getRemoteUrl("origin");
assertNotNull(storedUrl, "Remote URL should be stored");
assertEquals(urlWithCreds, storedUrl, "Stored URL should match");

URIish resolvedUrl = new URIish(storedUrl);
assertEquals("testuser", resolvedUrl.getUser(), "Username should be in URL");
assertEquals("testpass", resolvedUrl.getPass(), "Password should be in URL");
}
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test doesn't actually verify that the extractAndAddEmbeddedCredentials method is called or that credentials are properly extracted during a fetch operation. The test only verifies that a URL with embedded credentials can be stored in git config and retrieved. Consider adding a test that actually performs a fetch operation (or mocks it) to verify that credentials are extracted when resolving a remote name to a URL with embedded credentials. This would better align with the actual bug fix for JENKINS-69507.

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +49
/**
* Test that extractAndAddEmbeddedCredentials properly extracts username and password
* from a URL and adds them to the credentials provider.
*/
@Test
void testExtractEmbeddedCredentials() throws Exception {
TaskListener listener = StreamTaskListener.fromStdout();
JGitAPIImpl gitClient = new JGitAPIImpl(tempDir, listener);

URIish urlWithCredentials = new URIish("https://testuser:[email protected]/repo.git");

SmartCredentialsProvider provider = gitClient.getProvider();

var credsBefore = provider.getCredentials();
assertFalse(
credsBefore.containsKey("https://testuser:[email protected]/repo.git")
|| credsBefore.containsKey("https://example.com/repo.git"),
"Credentials should not exist before extraction");

gitClient.addCredentials(urlWithCredentials.toString(), createTestCredentials("testuser", "testpass"));

var credsAfter = provider.getCredentials();
assertTrue(credsAfter.size() > credsBefore.size(), "Credentials should be added");
}
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test manually calls addCredentials but doesn't verify that the extractAndAddEmbeddedCredentials method works correctly. The test description mentions "extractAndAddEmbeddedCredentials properly extracts username and password" but the method being tested is actually just the public addCredentials method. Since extractAndAddEmbeddedCredentials is private and called internally during fetch operations, this test doesn't actually validate the fix for JENKINS-69507. Consider renaming the test to better reflect what it's testing or adding integration tests that exercise the fetch operation.

Copilot uses AI. Check for mistakes.
Comment on lines +803 to +804
StandardUsernamePasswordCredentials embeddedCredentials =
new EmbeddedCredentials(user, pass, url.getHost());
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EmbeddedCredentials constructor receives url.getHost() which could potentially be null for certain URL formats (e.g., file URLs or malformed URLs). While the method extractAndAddEmbeddedCredentials checks for null URL and valid user/pass, it doesn't validate that url.getHost() is non-null before creating EmbeddedCredentials. Consider adding a null check for the host or handling this case gracefully.

Suggested change
StandardUsernamePasswordCredentials embeddedCredentials =
new EmbeddedCredentials(user, pass, url.getHost());
String host = url.getHost();
if (host == null || host.isEmpty()) {
host = "unknown-host";
}
StandardUsernamePasswordCredentials embeddedCredentials =
new EmbeddedCredentials(user, pass, host);

Copilot uses AI. Check for mistakes.
Comment on lines +768 to +771
@Override
@NonNull
public CredentialsDescriptor getDescriptor() {
throw new UnsupportedOperationException("Descriptor not available for embedded credentials");
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getDescriptor() method throws UnsupportedOperationException. While this is unlikely to be called for embedded credentials, if any code in the Jenkins ecosystem attempts to retrieve the descriptor (e.g., for serialization or UI purposes), it will fail. Consider implementing a minimal descriptor or documenting why this is safe to throw an exception here.

Suggested change
@Override
@NonNull
public CredentialsDescriptor getDescriptor() {
throw new UnsupportedOperationException("Descriptor not available for embedded credentials");
private static final CredentialsDescriptor DESCRIPTOR = new CredentialsDescriptor() {
@Override
public String getDisplayName() {
return "Embedded URL credentials";
}
};
@Override
@NonNull
public CredentialsDescriptor getDescriptor() {
return DESCRIPTOR;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Automated test addition or improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[JENKINS-69507] JGit fails 2nd and later https fetch with embedded username & password

1 participant