tools/mcp/dotnet/AzureSDKDevToolsMCP/Helpers/GitHelper.cs (116 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using AzureSDKDevToolsMCP.Services;
using LibGit2Sharp;
using Microsoft.Extensions.Logging;
namespace AzureSDKDevToolsMCP.Helpers
{
public interface IGitHelper
{
// Get the owner
public Task<string> GetRepoOwnerName(string path, bool findForkParent = true);
public string GetRepoName(string path);
public string GetRepoRootPath(string path);
public bool IsRepoPathForPublicSpecRepo(string path);
public string GetBranchName(string path);
public IList<string> GetChangedFiles(string path);
}
public class GitHelper(IGitHubService gitHubService, ILogger<GitHelper> logger) : IGitHelper
{
readonly ILogger<GitHelper> logger = logger;
readonly IGitHubService gitHubService = gitHubService;
readonly static string SPEC_REPO_NAME = "azure-rest-api-specs";
public IList<string> GetChangedFiles(string repoPath)
{
var changedFiles = new List<string>();
using (var repo = new Repository(repoPath))
{
// Get the current branch
Branch currentBranch = repo.Head;
logger.LogInformation($"Current branch: {currentBranch.FriendlyName}");
// Get the changes in the working directory
var changes = repo.Diff.Compare<TreeChanges>(currentBranch.Tip.Tree, DiffTargets.WorkingDirectory);
// List changed files
foreach (var change in changes)
{
changedFiles.Add(change.Path);
logger.LogInformation($"Changed file: {change.Path}");
}
}
return changedFiles;
}
public string GetBranchName(string repoPath)
{
using var repo = new Repository(repoPath);
var branchName = repo.Head.FriendlyName;
return branchName;
}
private static Uri GetRepoRemoteUri(string path)
{
using var repo = new Repository(path);
var remote = repo.Network?.Remotes["origin"];
if (remote != null)
{
return new Uri(remote.Url);
}
throw new InvalidOperationException("Unable to determine remote URL.");
}
public string GetRepoName(string path)
{
var uri = GetRepoRemoteUri(path);
var segments = uri.Segments;
if (segments.Length > 1)
{
return segments[^1].TrimEnd(".git".ToCharArray());
}
throw new InvalidOperationException("Unable to determine repository name.");
}
public async Task<string> GetRepoOwnerName(string path, bool findForkParent = true)
{
var uri = GetRepoRemoteUri(path);
var segments = uri.Segments;
string repoOwner = string.Empty;
string repoName = string.Empty;
if (segments.Length > 2)
{
repoOwner = segments[^2].TrimEnd('/');
repoName = segments[^1].TrimEnd(".git".ToCharArray());
}
if(findForkParent) {
// Check if the repo is a fork and get the parent repo
var parentRepoUrl = await gitHubService.GetGitHubParentRepoUrl(repoOwner, repoName);
logger.LogInformation($"Parent repo URL: {parentRepoUrl}");
if (!string.IsNullOrEmpty(parentRepoUrl))
{
var parentSegments = new Uri(parentRepoUrl).Segments;
if (parentSegments.Length > 2)
{
repoOwner = parentSegments[^2].TrimEnd('/');
}
}
}
if (!string.IsNullOrEmpty(repoOwner))
{
return repoOwner;
}
throw new InvalidOperationException("Unable to determine repository owner.");
}
public bool IsRepoPathForPublicSpecRepo(string path)
{
// Check if GitHub repo name of cloned path is "azure-rest-api-specs"
var uri = GetRepoRemoteUri(path);
return uri.ToString().Contains(SPEC_REPO_NAME);
}
public string GetRepoRootPath(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("path cannot be null or empty.", nameof(path));
}
if (Directory.Exists(Path.Combine(path, "specification")))
{
return path;
}
// Get absolute path for repo root from given path.
// Repo root is the parent of "specification" folder.
var currentDirectory = new DirectoryInfo(path);
while (currentDirectory != null && !currentDirectory.Name.Equals("specification"))
{
currentDirectory = currentDirectory.Parent;
}
return currentDirectory?.Parent?.FullName ?? string.Empty;
}
}
}