Skip to content
Open
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
2 changes: 2 additions & 0 deletions csharp/Platform.Bot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ private static async Task<int> Main(string[] args)
var githubStorage = new GitHubStorage(githubUserName, githubApiToken, githubApplicationName);
var issueTracker = new IssueTracker(githubStorage, new HelloWorldTrigger(githubStorage, dbContext, fileSetName), new OrganizationLastMonthActivityTrigger(githubStorage), new LastCommitActivityTrigger(githubStorage), new AdminAuthorIssueTriggerDecorator(new ProtectDefaultBranchTrigger(githubStorage), githubStorage), new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationRepositoriesDefaultBranchTrigger(githubStorage, dbContext), githubStorage), new AdminAuthorIssueTriggerDecorator(new ChangeOrganizationPullRequestsBaseBranchTrigger(githubStorage, dbContext), githubStorage));
var pullRequenstTracker = new PullRequestTracker(githubStorage, new MergeDependabotBumpsTrigger(githubStorage));
var commitTracker = new CommitTracker(githubStorage, new DependencyOnlyReleaseTrigger(githubStorage));
var timestampTracker = new DateTimeTracker(githubStorage, new CreateAndSaveOrganizationRepositoriesMigrationTrigger(githubStorage, dbContext, Path.Combine(Directory.GetCurrentDirectory(), "/github-migrations")));
var cancellation = new CancellationTokenSource();
while (true)
Expand All @@ -105,6 +106,7 @@ private static async Task<int> Main(string[] args)
{
await issueTracker.Start(cancellation.Token);
await pullRequenstTracker.Start(cancellation.Token);
await commitTracker.Start(cancellation.Token);
// timestampTracker.Start(cancellation.Token);
Thread.Sleep(minimumInteractionInterval);
}
Expand Down
91 changes: 91 additions & 0 deletions csharp/Platform.Bot/Trackers/CommitTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using Interfaces;
using Octokit;
using Storage.Remote.GitHub;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Platform.Collections.Lists;
using Platform.Threading;

namespace Platform.Bot.Trackers
{
/// <summary>
/// <para>
/// Represents the commit tracker.
/// </para>
/// <para></para>
/// </summary>
public class CommitTracker : ITracker<GitHubCommit>
{
/// <summary>
/// <para>
/// The git hub api.
/// </para>
/// <para></para>
/// </summary>
private GitHubStorage _storage;

/// <summary>
/// <para>
/// The triggers.
/// </para>
/// <para></para>
/// </summary>
private IList<ITrigger<GitHubCommit>> _triggers;

/// <summary>
/// <para>
/// Initializes a new <see cref="CommitTracker"/> instance.
/// </para>
/// <para></para>
/// </summary>
/// <param name="triggers">
/// <para>A triggers.</para>
/// <para></para>
/// </param>
/// <param name="storage">
/// <para>A git hub api.</para>
/// <para></para>
/// </param>
public CommitTracker(GitHubStorage storage, params ITrigger<GitHubCommit>[] triggers)
{
_storage = storage;
_triggers = triggers;
}

/// <summary>
/// <para>
/// Starts the cancellation token.
/// </para>
/// <para></para>
/// </summary>
/// <param name="cancellationToken">
/// <para>The cancellation token.</para>
/// <para></para>
/// </param>
public async Task Start(CancellationToken cancellationToken)
{
foreach (var trigger in _triggers)
{
foreach (var repository in _storage.Client.Repository.GetAllForOrg("linksplatform").AwaitResult())
{
// Get commits from the main branch only
var commits = _storage.GetCommits(repository.Id, new CommitRequest { Sha = repository.DefaultBranch }).AwaitResult();

foreach (var commit in commits)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}

if (await trigger.Condition(commit))
{
await trigger.Action(commit);
}
}
}
}
}
}
}
176 changes: 176 additions & 0 deletions csharp/Platform.Bot/Triggers/DependencyOnlyReleaseTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using System;
using System.Threading.Tasks;
using Interfaces;
using Octokit;
using Platform.Threading;
using Storage.Remote.GitHub;
using System.Linq;
using System.Text.RegularExpressions;
using Storage.Local;
using System.Collections.Generic;

namespace Platform.Bot.Triggers
{
public class DependencyOnlyReleaseTrigger : ITrigger<GitHubCommit>
{
private readonly GitHubStorage _githubStorage;
private readonly HashSet<string> _processedCommits;

public DependencyOnlyReleaseTrigger(GitHubStorage storage)
{
_githubStorage = storage;
_processedCommits = new HashSet<string>();
}

public async Task<bool> Condition(GitHubCommit commit)
{
try
{
// Skip if we already processed this commit
if (_processedCommits.Contains(commit.Sha))
{
return false;
}

// Skip if this is not a dependabot commit
if (!IsDependabotCommit(commit))
{
return false;
}

// Get the repository
var repositoryId = commit.Repository?.Id;
if (repositoryId == null)
{
return false;
}

// Check if this commit only changes dependency files
var commitDetails = _githubStorage.Client.Repository.Commit.Get(repositoryId.Value, commit.Sha).AwaitResult();

// Check if files changed are only dependency-related files
var changedFiles = commitDetails.Files;
if (changedFiles == null || !changedFiles.Any())
{
return false;
}

// Check if all changed files are dependency files
var isDependencyOnlyChange = changedFiles.All(file => IsDependencyFile(file.Filename));

if (!isDependencyOnlyChange)
{
return false;
}

// Check if a release with this commit already exists to avoid duplicates
var releases = _githubStorage.Client.Repository.Release.GetAll(repositoryId.Value).AwaitResult();
var existingRelease = releases.FirstOrDefault(r => r.TagName.Contains(commit.Sha.Substring(0, 7)));

return existingRelease == null;
}
catch (Exception ex)
{
Console.WriteLine($"Error in DependencyOnlyReleaseTrigger.Condition: {ex.Message}");
return false;
}
}

public async Task Action(GitHubCommit commit)
{
try
{
var repositoryId = commit.Repository?.Id;
if (repositoryId == null)
{
return;
}

var repository = _githubStorage.Client.Repository.Get(repositoryId.Value).AwaitResult();

// Generate a new version tag based on the current date and commit
var currentDate = DateTime.UtcNow;
var shortCommitSha = commit.Sha.Substring(0, 7);
var tagName = $"deps-{currentDate:yyyy.MM.dd}-{shortCommitSha}";

// Create release name and body
var releaseName = $"Dependency Updates - {currentDate:yyyy-MM-dd}";
var releaseBody = $"Automatic release for dependency updates.\n\nCommit: {commit.HtmlUrl}\nCommit Message: {commit.Commit.Message}\n\n🤖 Generated with [Claude Code](https://claude.ai/code)";

// Create the release
var newRelease = new NewRelease(tagName)
{
Name = releaseName,
Body = releaseBody,
Draft = false,
Prerelease = false,
TargetCommitish = commit.Sha
};

var createdRelease = await _githubStorage.Client.Repository.Release.Create(repositoryId.Value, newRelease);

Console.WriteLine($"Created automatic release for dependency updates: {createdRelease.HtmlUrl}");
Console.WriteLine($"Repository: {repository.FullName}");
Console.WriteLine($"Tag: {tagName}");

// Mark this commit as processed to avoid duplicates
_processedCommits.Add(commit.Sha);
}
catch (Exception ex)
{
Console.WriteLine($"Error in DependencyOnlyReleaseTrigger.Action: {ex.Message}");
}
}

private bool IsDependabotCommit(GitHubCommit commit)
{
// Check if the commit is from dependabot by checking the author or commit message
if (commit.Author?.Id == GitHubStorage.DependabotId)
{
return true;
}

if (commit.Committer?.Id == GitHubStorage.DependabotId)
{
return true;
}

// Check commit message patterns
var commitMessage = commit.Commit?.Message?.ToLower() ?? "";
var dependabotPatterns = new[]
{
"bump ",
"update ",
"dependabot",
"dependency"
};

return dependabotPatterns.Any(pattern => commitMessage.Contains(pattern));
}

private bool IsDependencyFile(string filename)
{
var dependencyFilePatterns = new[]
{
@"\.csproj$", // C# project files
@"packages\.config$", // NuGet packages.config
@"\.sln$", // Solution files (sometimes updated by dependabot)
@"package\.json$", // Node.js package.json
@"package-lock\.json$", // Node.js lock file
@"yarn\.lock$", // Yarn lock file
@"Cargo\.toml$", // Rust Cargo.toml
@"Cargo\.lock$", // Rust Cargo.lock
@"requirements\.txt$", // Python requirements
@"Pipfile$", // Python Pipfile
@"Pipfile\.lock$", // Python Pipfile.lock
@"pyproject\.toml$", // Python pyproject.toml
@"go\.mod$", // Go modules
@"go\.sum$", // Go sum file
@"Gemfile$", // Ruby Gemfile
@"Gemfile\.lock$" // Ruby Gemfile.lock
};

return dependencyFilePatterns.Any(pattern => Regex.IsMatch(filename, pattern, RegexOptions.IgnoreCase));
}
}
}
Loading