WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
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
20 changes: 20 additions & 0 deletions src/Agent.Plugins/GitCliManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,19 @@ public async Task<int> GitFetch(AgentTaskPluginExecutionContext context, string

//define options for fetch
string options = $"{forceTag} {tags} --prune {pruneTags} {progress} --no-recurse-submodules {remoteName} {depth} {string.Join(" ", filters.Select(filter => "--filter=" + filter))} {string.Join(" ", refSpec)}";

// When fetching with specific refspecs and tags enabled, git doesn't prune conflicting tag refs properly
// even with --prune-tags. Run git remote prune to clean up conflicting tag refs before fetch.
if (refSpec != null && refSpec.Count > 0 && fetchTags && !string.IsNullOrEmpty(pruneTags))
{
context.Debug("Running git remote prune before fetch to clean up conflicting tag refs.");
int pruneExitCode = await GitRemotePrune(context, repositoryPath, remoteName);
if (pruneExitCode != 0)
{
context.Debug($"Git remote prune completed with exit code {pruneExitCode}. Continuing with fetch.");
}
}

int retryCount = 0;
int fetchExitCode = 0;
while (retryCount < 3)
Expand Down Expand Up @@ -602,6 +615,13 @@ public async Task<int> GitPrune(AgentTaskPluginExecutionContext context, string
return await ExecuteGitCommandAsync(context, repositoryPath, "prune", "-v");
}

// git remote prune <remote>
public async Task<int> GitRemotePrune(AgentTaskPluginExecutionContext context, string repositoryPath, string remoteName)
{
context.Debug($"Prune remote tracking branches for remote: {remoteName}.");
return await ExecuteGitCommandAsync(context, repositoryPath, "remote", $"prune {remoteName}");
}

// git lfs prune
public async Task<int> GitLFSPrune(AgentTaskPluginExecutionContext context, string repositoryPath)
{
Expand Down
24 changes: 24 additions & 0 deletions src/Agent.Worker/Build/GitCommandManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public interface IGitCommandManager : IAgentService
// get remote set-url --push <origin> <url>
Task<int> GitRemoteSetPushUrl(IExecutionContext context, string repositoryPath, string remoteName, string remoteUrl);

// git remote prune <remote>
Task<int> GitRemotePrune(IExecutionContext context, string repositoryPath, string remoteName);

// git submodule foreach --recursive "git clean -ffdx"
Task<int> GitSubmoduleClean(IExecutionContext context, string repositoryPath);

Expand Down Expand Up @@ -295,6 +298,18 @@ public async Task<int> GitFetch(IExecutionContext context, string repositoryPath
//define options for fetch
string options = $"{tags} --prune {pruneTags} --progress --no-recurse-submodules {remoteName} {depth} {string.Join(" ", refSpec)}";

// When fetching with specific refspecs and tags enabled, git doesn't prune conflicting tag refs properly
// even with --prune-tags. Run git remote prune to clean up conflicting tag refs before fetch.
if (refSpec != null && refSpec.Count > 0 && fetchTags && !string.IsNullOrEmpty(pruneTags))
{
context.Debug("Running git remote prune before fetch to clean up conflicting tag refs.");
int pruneExitCode = await GitRemotePrune(context, repositoryPath, remoteName);
if (pruneExitCode != 0)
{
context.Debug($"Git remote prune completed with exit code {pruneExitCode}. Continuing with fetch.");
}
}

return await ExecuteGitCommandAsync(context, repositoryPath, "fetch", options, additionalCommandLine, cancellationToken);
}

Expand Down Expand Up @@ -408,6 +423,15 @@ public async Task<int> GitRemoteSetPushUrl(IExecutionContext context, string rep
return await ExecuteGitCommandAsync(context, repositoryPath, "remote", StringUtil.Format($"set-url --push {remoteName} {remoteUrl}"));
}

// git remote prune <remote>
public async Task<int> GitRemotePrune(IExecutionContext context, string repositoryPath, string remoteName)
{
ArgUtil.NotNull(context, nameof(context));

context.Debug($"Prune remote tracking branches for remote: {remoteName}.");
return await ExecuteGitCommandAsync(context, repositoryPath, "remote", StringUtil.Format($"prune {remoteName}"));
}

// git submodule foreach --recursive "git clean -ffdx"
public async Task<int> GitSubmoduleClean(IExecutionContext context, string repositoryPath)
{
Expand Down
48 changes: 48 additions & 0 deletions src/Test/L0/Worker/Build/GitSourceProviderL0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,54 @@ public void GetSourceGitFetchPR()
}
}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetSourceGitFetchPR_CallsGitRemotePrune()
{
using (TestHostContext tc = new TestHostContext(this))
{
var trace = tc.GetTrace();
// Arrange.
string dumySourceFolder = Path.Combine(tc.GetDirectory(WellKnownDirectory.Bin), "SourceProviderL0");
try
{
Directory.CreateDirectory(dumySourceFolder);
string dumyGitFolder = Path.Combine(dumySourceFolder, ".git");
Directory.CreateDirectory(dumyGitFolder);
string dumyGitConfig = Path.Combine(dumyGitFolder, "config");
File.WriteAllText(dumyGitConfig, "test git confg file");

var executionContext = GetTestExecutionContext(tc, dumySourceFolder, "refs/pull/12345/merge", "a596e13f5db8869f44574be0392fb8fe1e790ce4", false);
var endpoint = GetTestSourceEndpoint("https://github.com/microsoft/azure-pipelines-agent", false, false);

var _gitCommandManager = GetDefaultGitCommandMock();
tc.SetSingleton<IGitCommandManager>(_gitCommandManager.Object);
tc.SetSingleton<IVstsAgentWebProxy>(new VstsAgentWebProxy());
var _configStore = new Mock<IConfigurationStore>();
_configStore.Setup(x => x.GetSettings()).Returns(() => new AgentSettings() { ServerUrl = "http://localhost:8080/tfs" });
tc.SetSingleton<IConfigurationStore>(_configStore.Object);
tc.SetSingleton<IAgentCertificateManager>(new AgentCertificateManager());

GitSourceProvider gitSourceProvider = new ExternalGitSourceProvider();
gitSourceProvider.Initialize(tc);
gitSourceProvider.SetVariablesInEndpoint(executionContext.Object, endpoint);

// Act.
gitSourceProvider.GetSourceAsync(executionContext.Object, endpoint, default(CancellationToken)).GetAwaiter().GetResult();

// Assert.
// Verify that GitRemotePrune is called before GitFetch when we have refspecs and fetchTags is true
_gitCommandManager.Verify(x => x.GitRemotePrune(executionContext.Object, dumySourceFolder, "origin"), Times.Once);
_gitCommandManager.Verify(x => x.GitFetch(executionContext.Object, dumySourceFolder, "origin", It.IsAny<int>(), It.IsAny<bool>(), new List<string>() { "+refs/heads/*:refs/remotes/origin/*", "+refs/pull/12345/merge:refs/remotes/pull/12345/merge" }, It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
}
finally
{
IOUtil.DeleteDirectory(dumySourceFolder, CancellationToken.None);
}
}
}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
Expand Down