Skip to content

Commit

Permalink
feat: Add change files to control generated changelog file
Browse files Browse the repository at this point in the history
  • Loading branch information
philasmar committed Mar 18, 2024
1 parent 3cf7b69 commit 17458a7
Show file tree
Hide file tree
Showing 19 changed files with 309 additions and 26 deletions.
4 changes: 2 additions & 2 deletions .autover/autover.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"Projects": [
{
"Name": "AutoVer",
"Path": "src/AutoVer/AutoVer.csproj",
"IncrementType": "Patch",
"Changelog": []
"IncrementType": "Patch"
}
],
"UseCommitsForChangelog": false
Expand Down
10 changes: 10 additions & 0 deletions .autover/changes/1641dced-a01e-4511-a4e2-d8b0d60df721.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Projects": [
{
"Name": "AutoVer",
"ChangelogMessages": [
"Add change files to control generated changelog file"
]
}
]
}
10 changes: 10 additions & 0 deletions .autover/changes/e7ce1bf3-66dc-4266-a4a1-7c1b75da56e6.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"Projects": [
{
"Name": "AutoVer",
"ChangelogMessages": [
"Changelog command now uses the contents of last git tag instead of HEAD"
]
}
]
}
40 changes: 40 additions & 0 deletions src/AutoVer/Commands/ChangeCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using AutoVer.Models;
using AutoVer.Services;

namespace AutoVer.Commands;

public class ChangeCommand(
IConfigurationManager configurationManager,
IToolInteractiveService toolInteractiveService,
IChangeFileHandler changeFileHandler)
{
public async Task ExecuteAsync(
string? optionProjectPath,
string? optionIncrementType,
string? optionMessage)
{
if (!Enum.TryParse(optionIncrementType, out IncrementType incrementType))
{
incrementType = IncrementType.Patch;
}

if (string.IsNullOrEmpty(optionProjectPath))
optionProjectPath = Directory.GetCurrentDirectory();

var userConfiguration = await configurationManager.RetrieveUserConfiguration(optionProjectPath, incrementType);
if (userConfiguration.UseCommitsForChangelog)
{
toolInteractiveService.WriteErrorLine($"This repository is not configured to use change files. Change '{nameof(userConfiguration.UseCommitsForChangelog)}' to 'false' in the repo's '.autover/autover.json' file.");
return;
}
if (userConfiguration.Projects.Count > 1 && !string.IsNullOrEmpty(optionMessage))
{
toolInteractiveService.WriteErrorLine("You need to specify a project name with the change message. Use the '--project-name' argument to specify the project name.");
return;
}

var changeFile = changeFileHandler.GenerateChangeFile(userConfiguration, optionMessage);

await changeFileHandler.PersistChangeFile(userConfiguration, changeFile);
}
}
2 changes: 1 addition & 1 deletion src/AutoVer/Commands/ChangelogCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public async Task ExecuteAsync(

var userConfiguration = await configurationManager.RetrieveUserConfiguration(optionProjectPath, incrementType, tagName);

var changelogEntry = changelogHandler.GenerateChangelog(userConfiguration);
var changelogEntry = await changelogHandler.GenerateChangelog(userConfiguration);
if (optionReleaseName)
{
toolInteractiveService.WriteLine(changelogEntry.Title);
Expand Down
51 changes: 50 additions & 1 deletion src/AutoVer/Commands/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public class CommandFactory(
IToolInteractiveService toolInteractiveService,
IGitHandler gitHandler,
IConfigurationManager configurationManager,
IChangelogHandler changelogHandler
IChangelogHandler changelogHandler,
IChangeFileHandler changeFileHandler
) : ICommandFactory
{
private static readonly Option<string> OptionProjectPath = new("--project-path", Directory.GetCurrentDirectory, "Path to the project");
Expand All @@ -37,6 +38,7 @@ public Command BuildRootCommand()
{
rootCommand.Add(BuildVersionCommand());
rootCommand.Add(BuildChangelogCommand());
rootCommand.Add(BuildChangeCommand());
}

return rootCommand;
Expand Down Expand Up @@ -151,4 +153,51 @@ private Command BuildChangelogCommand()

return changelogCommand;
}

private Command BuildChangeCommand()
{
var changeCommand = new Command(
"change",
"Create a change file that contains information on the current changes.");

Option<string> messageOption = new(["-m", "--message"], "The change message for a given project.");

lock (ChildCommandLock)
{
changeCommand.Add(messageOption);
}

changeCommand.SetHandler(async (context) =>
{
try
{
var optionProjectPath = context.ParseResult.GetValueForOption(OptionProjectPath);
var optionIncrementType = context.ParseResult.GetValueForOption(OptionIncrementType);
var optionMessage = context.ParseResult.GetValueForOption(messageOption);

var command = new ChangeCommand(configurationManager, toolInteractiveService, changeFileHandler);
await command.ExecuteAsync(optionProjectPath, optionIncrementType, optionMessage);

context.ExitCode = CommandReturnCodes.Success;
}
catch (Exception e) when (e.IsExpectedException())
{
toolInteractiveService.WriteErrorLine(string.Empty);
toolInteractiveService.WriteErrorLine(e.Message);

context.ExitCode = CommandReturnCodes.UserError;
}
catch (Exception e)
{
// This is a bug
toolInteractiveService.WriteErrorLine(
"Unhandled exception.\r\nThis is a bug.\r\nPlease copy the stack trace below and file a bug at https://github.com/philasmar/autover. " +
e.PrettyPrint());

context.ExitCode = CommandReturnCodes.UnhandledException;
}
});

return changeCommand;
}
}
1 change: 1 addition & 0 deletions src/AutoVer/Constants/ConfigurationConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ namespace AutoVer.Constants;
public class ConfigurationConstants
{
public const string ConfigFolderName = ".autover";
public const string ChangesFolderName = "changes";
public const string ConfigFileName = "autover.json";
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static void AddCustomServices(this IServiceCollection serviceCollection,
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IConfigurationManager), typeof(ConfigurationManager), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IChangelogHandler), typeof(ChangelogHandler), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICommitHandler), typeof(ConventionalCommitHandler), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IChangeFileHandler), typeof(ChangeFileHandler), lifetime));

serviceCollection.AddSingleton<App>();
}
Expand Down
13 changes: 13 additions & 0 deletions src/AutoVer/Models/ChangeFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace AutoVer.Models;

public class ChangeFile
{
public List<ProjectChange> Projects { get; set; } = [];
}

public class ProjectChange
{
public required string Name { get; set; }

public List<string> ChangelogMessages { get; set; } = [];
}
3 changes: 1 addition & 2 deletions src/AutoVer/Models/UserConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ public class UserConfiguration

public class Project
{
public required string Name { get; set; }
public required string Path { get; set; }

[JsonConverter(typeof(JsonStringEnumConverter))]
public IncrementType IncrementType { get; set; } = IncrementType.Patch;

public string? PrereleaseLabel { get; set; }

public List<string> Changelog { get; set; } = [];

internal ProjectDefinition? ProjectDefinition { get; set; }
}
Expand Down
99 changes: 99 additions & 0 deletions src/AutoVer/Services/ChangeFileHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Text.Json;
using AutoVer.Constants;
using AutoVer.Exceptions;
using AutoVer.Models;
using AutoVer.Services.IO;

namespace AutoVer.Services;

public class ChangeFileHandler(
IPathManager pathManager,
IDirectoryManager directoryManager,
IFileManager fileManager,
IToolInteractiveService toolInteractiveService) : IChangeFileHandler
{
public ChangeFile GenerateChangeFile(UserConfiguration configuration, string? changeMessage)
{
var changeFile = new ChangeFile();
foreach (var project in configuration.Projects)
{
changeFile.Projects.Add(new ProjectChange
{
Name = project.Name,
ChangelogMessages = !string.IsNullOrEmpty(changeMessage) ? [changeMessage] : []
});
}

return changeFile;
}

public async Task PersistChangeFile(UserConfiguration configuration, ChangeFile changeFile)
{
if (string.IsNullOrEmpty(configuration.GitRoot))
throw new InvalidProjectException("The project path you have specified is not a valid git repository.");

var changeFolder = pathManager.Combine(configuration.GitRoot, ConfigurationConstants.ConfigFolderName,
ConfigurationConstants.ChangesFolderName);

if (!directoryManager.Exists(changeFolder))
directoryManager.CreateDirectory(changeFolder);

var changeFilePath = pathManager.Combine(changeFolder, $"{Guid.NewGuid().ToString().ToLower()}.json");

await fileManager.WriteAllTextAsync(changeFilePath,
JsonSerializer.Serialize(changeFile, new JsonSerializerOptions
{
WriteIndented = true
}));
}

public async Task<IList<ChangeFile>> LoadChangeFilesFromRepository(string repositoryRoot)
{
var changeFilesPath = pathManager.Combine(repositoryRoot, ConfigurationConstants.ConfigFolderName, ConfigurationConstants.ChangesFolderName);

var changeFilePaths = directoryManager.GetFiles(changeFilesPath, "*.json").ToList();

var changeFiles = new List<ChangeFile>();

foreach (var changeFilePath in changeFilePaths)
{
try
{
var content = await fileManager.ReadAllTextAsync(changeFilePath);
var changeFile = JsonSerializer.Deserialize<ChangeFile>(content);
if (changeFile != null)
changeFiles.Add(changeFile);
}
catch (Exception)
{
toolInteractiveService.WriteErrorLine($"Unable to deserialize the change file '{changeFilePath}'.");
}
}

return changeFiles;
}

public void ResetChangeFiles(UserConfiguration userConfiguration)
{
if (string.IsNullOrEmpty(userConfiguration.GitRoot))
throw new InvalidProjectException("The project path you have specified is not a valid git repository.");

var changeFolderPath = pathManager.Combine(userConfiguration.GitRoot, ConfigurationConstants.ConfigFolderName, ConfigurationConstants.ChangesFolderName);
if (!directoryManager.Exists(changeFolderPath))
return;

var changeFilePaths = directoryManager.GetFiles(changeFolderPath, "*", SearchOption.AllDirectories).ToList();

foreach (var changeFilePath in changeFilePaths)
{
try
{
fileManager.Delete(changeFilePath);
}
catch (Exception)
{
toolInteractiveService.WriteErrorLine($"Unable to delete the change file '{changeFilePath}'.");
}
}
}
}
62 changes: 45 additions & 17 deletions src/AutoVer/Services/ChangelogHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ namespace AutoVer.Services;
public class ChangelogHandler(
IGitHandler gitHandler,
IFileManager fileManager,
IPathManager pathManager) : IChangelogHandler
IPathManager pathManager,
IChangeFileHandler changeFileHandler) : IChangelogHandler
{
public ChangelogEntry GenerateChangelog(UserConfiguration configuration)
public async Task<ChangelogEntry> GenerateChangelog(UserConfiguration configuration)
{
if (string.IsNullOrEmpty(configuration.GitRoot))
throw new InvalidProjectException("The project path you have specified is not a valid git repository.");
Expand Down Expand Up @@ -95,25 +96,52 @@ public ChangelogEntry GenerateChangelog(UserConfiguration configuration)
}
else
{
var configuredProjects = new HashSet<string>();
foreach (var project in configuration.Projects)
{
if (project.ProjectDefinition is null)
throw new InvalidProjectException($"The project '{project.Path}' is invalid.");
if (project.Changelog.Count == 0)
continue;
var projectName = GetProjectName(project.ProjectDefinition.ProjectPath);
var changelogCategory = new ChangelogCategory
{
Name = projectName,
Version = project.ProjectDefinition?.Version
};
foreach (var entry in project.Changelog)
configuredProjects.Add(project.Name);
}

var changeFiles = await changeFileHandler.LoadChangeFilesFromRepository(configuration.GitRoot);
foreach (var changeFile in changeFiles)
{
changeFile.Projects.RemoveAll(x => !configuredProjects.Contains(x.Name));
}

var changelogCategories = new Dictionary<string, ChangelogCategory>();
foreach (var changeFile in changeFiles)
{
foreach (var project in changeFile.Projects)
{
changelogCategory.Changes.Add(new ChangelogChange { Description = entry });
if (changelogCategories.TryGetValue(project.Name, out var category))
{
foreach (var changelogMessage in project.ChangelogMessages)
{
category.Changes.Add(new ChangelogChange { Description = changelogMessage });
}
}
else
{
if (project.ChangelogMessages.Count == 0)
continue;

var configuredProject = configuration.Projects.First(x => x.Name.Equals(project.Name));
if (configuredProject.ProjectDefinition is null)
throw new InvalidProjectException($"The project '{configuredProject.Path}' is invalid.");

var changelogCategory = new ChangelogCategory
{
Name = configuredProject.Name,
Version = configuredProject.ProjectDefinition?.Version
};
foreach (var changelogMessage in project.ChangelogMessages)
{
changelogCategory.Changes.Add(new ChangelogChange { Description = changelogMessage });
}
changelogEntry.ChangelogCategories.Add(changelogCategory);
changelogCategories.Add(configuredProject.Name, changelogCategory);
}
}
changelogEntry.ChangelogCategories.Add(changelogCategory);
}
}

Expand Down
Loading

0 comments on commit 17458a7

Please sign in to comment.