diff --git a/src/Agent.Plugins/GitSourceProvider.cs b/src/Agent.Plugins/GitSourceProvider.cs index 3d1c871d17..a429ad9321 100644 --- a/src/Agent.Plugins/GitSourceProvider.cs +++ b/src/Agent.Plugins/GitSourceProvider.cs @@ -802,6 +802,24 @@ public async Task GetSourceAsync( { additionalCheckoutArgs.Add(args); } + + // When using partial clones (fetch filters), set up authentication in git config + // so that git's automatic promisor fetches can access credentials + if (additionalFetchFilterOptions.Any()) + { + string authHeader = GenerateAuthHeader(executionContext, username, password, useBearerAuthType); + string configValue = $"AUTHORIZATION: {authHeader}"; + executionContext.Debug("Setting up git config authentication for partial clone promisor fetches."); + int exitCode_configAuth = await gitCommandManager.GitConfig(executionContext, targetPath, configKey, configValue); + if (exitCode_configAuth != 0) + { + executionContext.Warning($"Failed to set git config authentication for partial clone. Promisor fetches may fail. Exit code: {exitCode_configAuth}"); + } + else + { + configModifications[configKey] = configValue; + } + } } else { diff --git a/src/Test/L0/Plugin/TestGitSourceProvider/GitSourceProviderL0.cs b/src/Test/L0/Plugin/TestGitSourceProvider/GitSourceProviderL0.cs index 39e975550a..17b46a7283 100644 --- a/src/Test/L0/Plugin/TestGitSourceProvider/GitSourceProviderL0.cs +++ b/src/Test/L0/Plugin/TestGitSourceProvider/GitSourceProviderL0.cs @@ -6,6 +6,8 @@ using Xunit; using System.IO; using System; +using System.Linq; +using System.Threading.Tasks; using Moq; using Agent.Plugins.Repository; using System.Collections.Generic; @@ -112,6 +114,32 @@ public void TestSetWSICConnection() Assert.Contains("dev.azure.com/test/_git/myrepo", tc.TaskVariables.GetValueOrDefault("repoUrlWithCred").Value); } + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Plugin")] + public async Task TestPartialCloneAuthenticationConfigSetup() + { + using TestHostContext hc = new(this); + MockAgentTaskPluginExecutionContext tc = new(hc.GetTrace()); + var gitCliManagerMock = new MockGitCliManager(); + + // Arrange - simulate authenticated git source provider with partial clone (fetch filter) + tc.Inputs["fetchFilter"] = "blob:none"; + tc.Variables.Add("UseFetchFilterInCheckoutTask", "true"); + + var repositoryPath = Path.Combine(getWorkFolder(hc), "1", "testrepo"); + + // Test the specific authentication config logic for partial clones + var gitSourceProvider = new TestAuthenticatedGitSourceProvider(); + await gitSourceProvider.TestGitConfigForPartialClone(tc, gitCliManagerMock, repositoryPath, "test-user", "test-token", false); + + // Assert - verify that git config command was called with authentication header + var configCalls = gitCliManagerMock.GitCommandCallsOptions.FindAll(call => + call.Contains("config") && call.Contains("http.extraheader") && call.Contains("AUTHORIZATION:")); + + Assert.True(configCalls.Count > 0, "Expected git config call with authentication header for partial clone, but none found"); + } + private Pipelines.RepositoryResource GetRepository(TestHostContext hostContext, String alias, String relativePath) { var workFolder = hostContext.GetDirectory(WellKnownDirectory.Work); @@ -126,3 +154,28 @@ private Pipelines.RepositoryResource GetRepository(TestHostContext hostContext, return repo; } } + +// Test helper class to expose the relevant functionality for testing +internal class TestAuthenticatedGitSourceProvider : AuthenticatedGitSourceProvider +{ + public override bool GitSupportsFetchingCommitBySha1Hash(GitCliManager gitCommandManager) + { + return true; + } + + // Method to test the specific authentication config logic for partial clones + public async Task TestGitConfigForPartialClone(AgentTaskPluginExecutionContext executionContext, IGitCliManager gitCommandManager, string targetPath, string username, string password, bool useBearerAuthType) + { + // Simulate the condition where we have fetch filter options (partial clone) + var additionalFetchFilterOptions = new List { "blob:none" }; + + // Simulate the authentication setup logic for partial clones + if (additionalFetchFilterOptions.Any()) + { + string authHeader = GenerateAuthHeader(executionContext, username, password, useBearerAuthType); + string configValue = $"AUTHORIZATION: {authHeader}"; + executionContext.Debug("Setting up git config authentication for partial clone promisor fetches."); + await gitCommandManager.GitConfig(executionContext, targetPath, "http.extraheader", configValue); + } + } +} diff --git a/src/Test/L0/Plugin/TestGitSourceProvider/MockGitCliManager.cs b/src/Test/L0/Plugin/TestGitSourceProvider/MockGitCliManager.cs index 78256742c0..4044d6e558 100644 --- a/src/Test/L0/Plugin/TestGitSourceProvider/MockGitCliManager.cs +++ b/src/Test/L0/Plugin/TestGitSourceProvider/MockGitCliManager.cs @@ -11,6 +11,7 @@ public class MockGitCliManager : GitCliManager { public List GitCommandCallsOptions = new List(); public bool IsLfsConfigExistsing = false; + public override Task GitVersion(AgentTaskPluginExecutionContext context) { return Task.FromResult(new Version("2.30.2")); @@ -21,6 +22,12 @@ public override Task GitLfsVersion(AgentTaskPluginExecutionContext cont return Task.FromResult(new Version("2.30.2")); } + public override Task GitConfig(AgentTaskPluginExecutionContext context, string repositoryPath, string configKey, string configValue) + { + GitCommandCallsOptions.Add($"{repositoryPath},config,{configKey} {configValue},"); + return Task.FromResult(0); + } + protected override Task ExecuteGitCommandAsync(AgentTaskPluginExecutionContext context, string repoRoot, string command, string options, IList output) { return Task.FromResult(0);