Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/Agent.Plugins/GitSourceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
53 changes: 53 additions & 0 deletions src/Test/L0/Plugin/TestGitSourceProvider/GitSourceProviderL0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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<string> { "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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class MockGitCliManager : GitCliManager
{
public List<string> GitCommandCallsOptions = new List<string>();
public bool IsLfsConfigExistsing = false;

public override Task<Version> GitVersion(AgentTaskPluginExecutionContext context)
{
return Task.FromResult(new Version("2.30.2"));
Expand All @@ -21,6 +22,12 @@ public override Task<Version> GitLfsVersion(AgentTaskPluginExecutionContext cont
return Task.FromResult(new Version("2.30.2"));
}

public override Task<int> GitConfig(AgentTaskPluginExecutionContext context, string repositoryPath, string configKey, string configValue)
{
GitCommandCallsOptions.Add($"{repositoryPath},config,{configKey} {configValue},");
return Task.FromResult(0);
}

protected override Task<int> ExecuteGitCommandAsync(AgentTaskPluginExecutionContext context, string repoRoot, string command, string options, IList<string> output)
{
return Task.FromResult(0);
Expand Down