Skip to content
Merged
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
4 changes: 2 additions & 2 deletions eng/update-dependencies/FromStagingPipelineCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ public static async Task<GitRepoContext> CreateAsync(
var prBranch = options.CreatePrBranchName($"update-deps-int-{options.StageContainer}", buildId);
var committer = options.GetCommitterIdentity();

// Clone the repo
var git = await gitRepoFactory.CreateAndCloneAsync(remoteUrl);
// Clone the repo and configure git identity for commits
var git = await gitRepoFactory.CreateAndCloneAsync(remoteUrl, gitIdentity: committer);
// Ensure the branch we want to modify exists, then check it out
await git.Remote.EnsureBranchExistsAsync(targetBranch);
// Create a new branch to push changes to and create a PR from
Expand Down
29 changes: 26 additions & 3 deletions eng/update-dependencies/Git/GitRepoHelperFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ internal interface IGitRepoHelperFactory
/// defaults to a temporary directory. The caller is responsible for
/// managing/cleaning up the temporary directory.
/// </param>
Task<IGitRepoHelper> CreateAndCloneAsync(string repoUri, string? localCloneDir = null);
/// <param name="gitIdentity">
/// The git identity (name and email) to configure on the cloned repository.
/// This is required for commits to succeed in environments where git is not
/// globally configured (e.g., CI/CD pipelines).
/// </param>
Task<IGitRepoHelper> CreateAndCloneAsync(
string repoUri,
string? localCloneDir = null,
(string Name, string Email)? gitIdentity = null);

/// <summary>
/// Creates an <see cref="IGitRepoHelper"/> that points to an existing
Expand Down Expand Up @@ -54,7 +62,10 @@ IServiceProvider serviceProvider
private readonly IServiceProvider _serviceProvider = serviceProvider;

/// <inheritdoc/>
public async Task<IGitRepoHelper> CreateAndCloneAsync(string repoUri, string? localCloneDir = null)
public async Task<IGitRepoHelper> CreateAndCloneAsync(
string repoUri,
string? localCloneDir = null,
(string Name, string Email)? gitIdentity = null)
{
localCloneDir ??= Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

Expand All @@ -66,7 +77,19 @@ await _gitRepoCloner.CloneAsync(
gitDirectory: null);
_logger.LogInformation("Cloned '{RepoUri}' to '{LocalCloneDir}'", repoUri, localCloneDir);

return CreateFromLocal(repoUri, localCloneDir);
var gitRepoHelper = CreateFromLocal(repoUri, localCloneDir);

if (gitIdentity is { } identity)
{
var localGitRepo = _localGitRepoFactory.Create(new NativePath(localCloneDir));
await localGitRepo.SetConfigValue("user.name", identity.Name);
await localGitRepo.SetConfigValue("user.email", identity.Email);
_logger.LogInformation(
"Configured git identity: {Name} <{Email}>",
identity.Name, identity.Email);
}

return gitRepoHelper;
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ public override async Task<int> ExecuteAsync(SyncInternalReleaseOptions options)
$"The source branch '{options.SourceBranch}' cannot be an internal branch.");
}

using var repo = await _gitRepoHelperFactory.CreateAndCloneAsync(remoteUrl);
// Get git identity if available (required for commits, optional for read-only operations)
var gitIdentity = !string.IsNullOrWhiteSpace(options.User) && !string.IsNullOrWhiteSpace(options.Email)
? options.GetCommitterIdentity()
: ((string, string)?)null;

using var repo = await _gitRepoHelperFactory.CreateAndCloneAsync(
remoteUrl,
gitIdentity: gitIdentity);

// Verify that the source branch exists on the remote.
var sourceBranchExists = await repo.Remote.RemoteBranchExistsAsync(options.SourceBranch);
Expand Down
8 changes: 4 additions & 4 deletions tests/UpdateDependencies.Tests/SyncInternalReleaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public async Task CreateInternalBranch()

var repoMock = new Mock<IGitRepoHelper>();
var repoFactoryMock = new Mock<IGitRepoHelperFactory>();
repoFactoryMock.Setup(f => f.CreateAndCloneAsync(options.GetAzdoRepoUrl(), null)).ReturnsAsync(repoMock.Object);
repoFactoryMock.Setup(f => f.CreateAndCloneAsync(options.GetAzdoRepoUrl(), null, It.IsAny<(string, string)?>())).ReturnsAsync(repoMock.Object);

// Setup:
// Target branch does not exist on remote
Expand Down Expand Up @@ -112,7 +112,7 @@ public async Task AlreadyUpToDate()
// not explicitly set up in this test.
var repoMock = new Mock<IGitRepoHelper>(MockBehavior.Strict);
var repoFactoryMock = new Mock<IGitRepoHelperFactory>();
repoFactoryMock.Setup(f => f.CreateAndCloneAsync(options.GetAzdoRepoUrl(), null)).ReturnsAsync(repoMock.Object);
repoFactoryMock.Setup(f => f.CreateAndCloneAsync(options.GetAzdoRepoUrl(), null, It.IsAny<(string, string)?>())).ReturnsAsync(repoMock.Object);

// Setup: Both target and source branches exist on remote.
repoMock.Setup(r => r.Remote.RemoteBranchExistsAsync(options.TargetBranch)).ReturnsAsync(true);
Expand Down Expand Up @@ -149,7 +149,7 @@ public async Task FastForward()
repoMock.Setup(r => r.Remote).Returns(remoteRepoMock.Object);

var repoFactoryMock = new Mock<IGitRepoHelperFactory>();
repoFactoryMock.Setup(f => f.CreateAndCloneAsync(options.GetAzdoRepoUrl(), null)).ReturnsAsync(repoMock.Object);
repoFactoryMock.Setup(f => f.CreateAndCloneAsync(options.GetAzdoRepoUrl(), null, It.IsAny<(string, string)?>())).ReturnsAsync(repoMock.Object);

// Setup: Both target and source branches exist on remote.
repoMock.Setup(r => r.Remote.RemoteBranchExistsAsync(options.TargetBranch)).ReturnsAsync(true);
Expand Down Expand Up @@ -336,7 +336,7 @@ private class GitTestScenario
public GitTestScenario(string localRepoPath, string remoteRepoUrl)
{
RepoMock.Setup(r => r.Local.LocalPath).Returns(localRepoPath);
RepoFactoryMock.Setup(f => f.CreateAndCloneAsync(remoteRepoUrl, null)).ReturnsAsync(RepoMock.Object);
RepoFactoryMock.Setup(f => f.CreateAndCloneAsync(remoteRepoUrl, null, It.IsAny<(string, string)?>())).ReturnsAsync(RepoMock.Object);
}

/// <summary>
Expand Down