diff --git a/source/Git/CreativeCoders.Git.Abstractions/GitCommands/FetchTagsCommandOptions.cs b/source/Git/CreativeCoders.Git.Abstractions/GitCommands/FetchTagsCommandOptions.cs new file mode 100644 index 0000000..3051eba --- /dev/null +++ b/source/Git/CreativeCoders.Git.Abstractions/GitCommands/FetchTagsCommandOptions.cs @@ -0,0 +1,8 @@ +namespace CreativeCoders.Git.Abstractions.GitCommands; + +public class FetchTagsCommandOptions +{ + public bool Prune { get; set; } = true; + + public string RemoteName { get; set; } = "origin"; +} diff --git a/source/Git/CreativeCoders.Git.Abstractions/GitCommands/IFetchTagsCommand.cs b/source/Git/CreativeCoders.Git.Abstractions/GitCommands/IFetchTagsCommand.cs new file mode 100644 index 0000000..fcd6a0c --- /dev/null +++ b/source/Git/CreativeCoders.Git.Abstractions/GitCommands/IFetchTagsCommand.cs @@ -0,0 +1,6 @@ +namespace CreativeCoders.Git.Abstractions.GitCommands; + +public interface IFetchTagsCommand +{ + void Execute(FetchTagsCommandOptions commandOptions); +} diff --git a/source/Git/CreativeCoders.Git.Abstractions/GitCommands/IGitCommands.cs b/source/Git/CreativeCoders.Git.Abstractions/GitCommands/IGitCommands.cs index d719295..9542164 100644 --- a/source/Git/CreativeCoders.Git.Abstractions/GitCommands/IGitCommands.cs +++ b/source/Git/CreativeCoders.Git.Abstractions/GitCommands/IGitCommands.cs @@ -5,4 +5,6 @@ public interface IGitCommands IPullCommand CreatePullCommand(); IPushCommand CreatePushCommand(); + + IFetchTagsCommand CreateFetchTagsCommand(); } diff --git a/source/Git/CreativeCoders.Git.Abstractions/Tags/IGitTag.cs b/source/Git/CreativeCoders.Git.Abstractions/Tags/IGitTag.cs index 39ea9d5..7741689 100644 --- a/source/Git/CreativeCoders.Git.Abstractions/Tags/IGitTag.cs +++ b/source/Git/CreativeCoders.Git.Abstractions/Tags/IGitTag.cs @@ -11,4 +11,6 @@ public interface IGitTag : IEquatable, IComparable, INamedRef string TargetSha { get; } IGitCommit? PeeledTargetCommit(); -} \ No newline at end of file + + IGitCommit? TargetCommit { get; } +} diff --git a/source/Git/CreativeCoders.Git.Abstractions/Tags/IGitTagCollection.cs b/source/Git/CreativeCoders.Git.Abstractions/Tags/IGitTagCollection.cs index 23c2277..d620e8a 100644 --- a/source/Git/CreativeCoders.Git.Abstractions/Tags/IGitTagCollection.cs +++ b/source/Git/CreativeCoders.Git.Abstractions/Tags/IGitTagCollection.cs @@ -4,15 +4,19 @@ namespace CreativeCoders.Git.Abstractions.Tags; public interface IGitTagCollection : IEnumerable { - IGitTag CreateTag(string tagName); + IGitTag CreateTag(string tagName, string? objectish = null); - IGitTag CreateTag(string tagName, string objectish); + IGitTag CreateTagWithMessage(string tagName, string message, string? objectish = null); - IGitTag CreateTagWithMessage(string tagName, string message); + void DeleteTag(string tagName, bool deleteOnRemote = false); - IGitTag CreateTagWithMessage(string tagName, string objectish, string message); + void DeleteTag(IGitTag tag, bool deleteOnRemote = false); + + void DeleteRemoteTag(string tagName); + + void PushTag(string tagName); void PushTag(IGitTag tag); void PushAllTags(); -} \ No newline at end of file +} diff --git a/source/Git/CreativeCoders.Git/GitCommands/FetchTagsCommand.cs b/source/Git/CreativeCoders.Git/GitCommands/FetchTagsCommand.cs new file mode 100644 index 0000000..c041bdf --- /dev/null +++ b/source/Git/CreativeCoders.Git/GitCommands/FetchTagsCommand.cs @@ -0,0 +1,21 @@ +using CreativeCoders.Git.Abstractions.GitCommands; + +namespace CreativeCoders.Git.GitCommands; + +internal class FetchTagsCommand(RepositoryContext repositoryContext) : IFetchTagsCommand +{ + private readonly RepositoryContext _repositoryContext = Ensure.NotNull(repositoryContext); + + public void Execute(FetchTagsCommandOptions commandOptions) + { + var fetchOptions = new FetchOptions + { + Prune = commandOptions.Prune, + TagFetchMode = TagFetchMode.All + }; + + Commands.Fetch(_repositoryContext.LibGitRepository, commandOptions.RemoteName, ["+refs/tags/*:refs/tags/*"], + fetchOptions, + "Fetch all tags"); + } +} diff --git a/source/Git/CreativeCoders.Git/GitCommands/GitCommands.cs b/source/Git/CreativeCoders.Git/GitCommands/GitCommands.cs index 7585590..825fe78 100644 --- a/source/Git/CreativeCoders.Git/GitCommands/GitCommands.cs +++ b/source/Git/CreativeCoders.Git/GitCommands/GitCommands.cs @@ -20,4 +20,9 @@ public IPushCommand CreatePushCommand() { return new PushCommand(_repositoryContext); } + + public IFetchTagsCommand CreateFetchTagsCommand() + { + return new FetchTagsCommand(_repositoryContext); + } } diff --git a/source/Git/CreativeCoders.Git/Tags/GitTag.cs b/source/Git/CreativeCoders.Git/Tags/GitTag.cs index d356f2d..31dd5d6 100644 --- a/source/Git/CreativeCoders.Git/Tags/GitTag.cs +++ b/source/Git/CreativeCoders.Git/Tags/GitTag.cs @@ -14,6 +14,11 @@ internal GitTag(Tag tag) _tag = Ensure.NotNull(tag); Name = new ReferenceName(_tag.CanonicalName); + + if (_tag.Target is Commit commit) + { + TargetCommit = GitCommit.From(commit); + } } static GitTag() => InitComparableObject(x => x.Name.Canonical); @@ -34,5 +39,7 @@ internal GitTag(Tag tag) return GitCommit.From(target as Commit); } + public IGitCommit? TargetCommit { get; } + public static implicit operator Tag(GitTag tag) => tag._tag; } diff --git a/source/Git/CreativeCoders.Git/Tags/GitTagCollection.cs b/source/Git/CreativeCoders.Git/Tags/GitTagCollection.cs index 4f50eed..5d49ee0 100644 --- a/source/Git/CreativeCoders.Git/Tags/GitTagCollection.cs +++ b/source/Git/CreativeCoders.Git/Tags/GitTagCollection.cs @@ -19,29 +19,49 @@ internal GitTagCollection(RepositoryContext context) _libGitCaller = _context.LibGitCaller; } - public IGitTag CreateTag(string tagName) + public IGitTag CreateTag(string tagName, string? objectish = null) { - return new GitTag(_libGitCaller.Invoke(() => _repository.ApplyTag(tagName))); + return string.IsNullOrWhiteSpace(objectish) + ? new GitTag(_libGitCaller.Invoke(() => _repository.ApplyTag(tagName))) + : new GitTag(_libGitCaller.Invoke(() => _repository.ApplyTag(tagName, objectish))); } - public IGitTag CreateTag(string tagName, string objectish) + public IGitTag CreateTagWithMessage(string tagName, string message, string? objectish = null) { - return new GitTag(_libGitCaller.Invoke(() => _repository.ApplyTag(tagName, objectish))); + return string.IsNullOrWhiteSpace(objectish) + ? new GitTag(_libGitCaller.Invoke(() => _repository.ApplyTag(tagName, _context.GetSignature(), message))) + : new GitTag(_libGitCaller + .Invoke(() => _repository.ApplyTag(tagName, objectish, _context.GetSignature(), message))); } - public IGitTag CreateTagWithMessage(string tagName, string message) + public void DeleteTag(string tagName, bool deleteOnRemote = false) { - return new GitTag(_libGitCaller.Invoke(() => _repository.ApplyTag(tagName, _context.GetSignature(), message))); + _libGitCaller.Invoke(() => _repository.Tags.Remove(tagName)); + + if (deleteOnRemote) + { + DeleteRemoteTag(tagName); + } } - public IGitTag CreateTagWithMessage(string tagName, string objectish, string message) + public void DeleteTag(IGitTag tag, bool deleteOnRemote = false) { - return new GitTag( - _libGitCaller - .Invoke(() => _repository.ApplyTag(tagName, objectish, _context.GetSignature(), message))); + DeleteTag(tag.Name.Canonical, deleteOnRemote); } - public void PushTag(IGitTag tag) + public void DeleteRemoteTag(string tagName) + { + var pushOptions = new PushOptions + { + CredentialsProvider = _context.GetCredentialsHandler() + }; + + _libGitCaller.Invoke(() => + _repository.Network.Push(_repository.Network.Remotes[GitRemotes.Origin], $":refs/tags/{tagName}", + pushOptions)); + } + + public void PushTag(string tagName) { var pushOptions = new PushOptions { @@ -49,7 +69,12 @@ public void PushTag(IGitTag tag) }; _libGitCaller.Invoke(() => - _repository.Network.Push(_repository.Network.Remotes[GitRemotes.Origin], tag.Name.Canonical, pushOptions)); + _repository.Network.Push(_repository.Network.Remotes[GitRemotes.Origin], tagName, pushOptions)); + } + + public void PushTag(IGitTag tag) + { + PushTag(tag.Name.Canonical); } public void PushAllTags() diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/Info/InfoBranchesCommand.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/Info/InfoBranchesCommand.cs index 4d36a3f..6ecc288 100644 --- a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/Info/InfoBranchesCommand.cs +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/Info/InfoBranchesCommand.cs @@ -46,8 +46,6 @@ public Task ExecuteAsync(InfoBranchesOptions options) _ansiConsole.PrintCommitLog(commits); } - _ansiConsole.WriteLine(); - return Task.FromResult(CommandResult.Success); } } diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/List/ListBranchesCommand.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/List/ListBranchesCommand.cs index 793baef..877de82 100644 --- a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/List/ListBranchesCommand.cs +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/List/ListBranchesCommand.cs @@ -41,7 +41,6 @@ private void PrintBranch(IGitBranch branch, int column0Width, int column1Width) public Task ExecuteAsync(ListBranchesOptions options) { _sysConsole - .WriteLine() .WriteLine("List all branches:") .WriteLine(); @@ -62,8 +61,6 @@ public Task ExecuteAsync(ListBranchesOptions options) // ReSharper disable once AccessToDisposedClosure branches.ForEach(branch => PrintBranch(branch, column0Width, column1Width)); - _sysConsole.WriteLine(); - return Task.FromResult(CommandResult.Success); } } diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/Update/UpdateBranchesCommand.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/Update/UpdateBranchesCommand.cs index 6aa8181..f0a2404 100644 --- a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/Update/UpdateBranchesCommand.cs +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/BranchGroup/Update/UpdateBranchesCommand.cs @@ -37,7 +37,6 @@ public async Task ExecuteAsync(UpdateBranchesOptions options) Ensure.NotNull(options); _ansiConsole - .EmptyLine() .WriteMarkupLine(_cml.Caption("Update permanent local branches")) .EmptyLine(); @@ -73,8 +72,6 @@ public async Task ExecuteAsync(UpdateBranchesOptions options) _gitRepository.Branches.CheckOut(currentBranch.Name.Friendly); } - _ansiConsole.EmptyLine(); - return CommandResult.Success; } diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/ReleaseGroup/Create/CreateReleaseCommand.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/ReleaseGroup/Create/CreateReleaseCommand.cs index ca4bf1c..5944a16 100644 --- a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/ReleaseGroup/Create/CreateReleaseCommand.cs +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/ReleaseGroup/Create/CreateReleaseCommand.cs @@ -55,7 +55,7 @@ public async Task ExecuteAsync(CreateReleaseOptions options) _gitRepository.Pull(); var versionTag = - _gitRepository.Tags.CreateTagWithMessage(tagName, mainBranchName, $"Version {options.Version}"); + _gitRepository.Tags.CreateTagWithMessage(tagName, $"Version {options.Version}", mainBranchName); if (options.PushAllTags) { diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/Shared/BoolExtensions.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/Shared/BoolExtensions.cs new file mode 100644 index 0000000..875b3dd --- /dev/null +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/Shared/BoolExtensions.cs @@ -0,0 +1,42 @@ +namespace CreativeCoders.GitTool.Cli.Commands.Shared; + +public static class BoolExtensions +{ + public static void IfElse(this bool condition, Action trueAction, Action falseAction) + { + if (condition) + { + trueAction(); + } + else + { + falseAction(); + } + } + + public static T IfElse(this bool condition, Func trueFunc, Func falseFunc) + { + return condition ? trueFunc() : falseFunc(); + } + + public static bool If(this bool condition, Action action) + { + if (!condition) + { + return false; + } + + action(); + return true; + } + + public static void Else(this bool condition, Action action) + { + if (condition) + { + return; + } + + action(); + } +} diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/Shared/GitToolPullCommand.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/Shared/GitToolPullCommand.cs index dd15d6a..bb2f9c4 100644 --- a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/Shared/GitToolPullCommand.cs +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/Shared/GitToolPullCommand.cs @@ -38,8 +38,6 @@ public Task ExecuteAsync(IGitRepository gitRepository, bool verbose) PrintMergeResultStatus(mergeResult.MergeStatus); - _ansiConsole.WriteLine(); - return Task.FromResult(0); } diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/Shared/GitToolPushCommand.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/Shared/GitToolPushCommand.cs index a645653..3496b4e 100644 --- a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/Shared/GitToolPushCommand.cs +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/Shared/GitToolPushCommand.cs @@ -42,8 +42,6 @@ public Task ExecuteAsync(IGitRepository gitRepository, bool createRemoteIsN pushCommand.Run(); - _ansiConsole.WriteLine(); - return Task.FromResult(0); } diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Create/CreateTagCommand.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Create/CreateTagCommand.cs new file mode 100644 index 0000000..ad97275 --- /dev/null +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Create/CreateTagCommand.cs @@ -0,0 +1,50 @@ +using CreativeCoders.Cli.Core; +using CreativeCoders.Core; +using CreativeCoders.Git.Abstractions; +using CreativeCoders.Git.Abstractions.Tags; +using CreativeCoders.SysConsole.Core; +using JetBrains.Annotations; +using Spectre.Console; + +namespace CreativeCoders.GitTool.Cli.Commands.TagGroup.Create; + +[UsedImplicitly] +[CliCommand([TagCommandGroup.Name, "create"], Description = "Creates a new tag")] +public class CreateTagCommand(IAnsiConsole ansiConsole, IGitRepository gitRepository) : ICliCommand +{ + private readonly IAnsiConsole _ansiConsole = Ensure.NotNull(ansiConsole); + + private readonly IGitRepository _gitRepository = Ensure.NotNull(gitRepository); + + public Task ExecuteAsync(CreateTagOptions options) + { + _ansiConsole.WriteLine($"Creating tag '{options.TagName}'..."); + + var tag = CreateTag(options); + + _ansiConsole.MarkupLines( + $"Tag '{tag.Name.Friendly}' created.".ToSuccessMarkup(), + string.Empty, + $"Target commit: {tag.TargetCommit?.Id.Sha ?? "[none]"}".ToEscapedMarkup()); + + if (options.PushAfterCreate) + { + _ansiConsole.WriteLines( + string.Empty, + "Pushing tag after creation"); + + _gitRepository.Tags.PushTag(tag); + + _ansiConsole.MarkupLine("Tag pushed.".ToSuccessMarkup()); + } + + return Task.FromResult(CommandResult.Success); + } + + private IGitTag CreateTag(CreateTagOptions options) + { + return string.IsNullOrWhiteSpace(options.Message) + ? _gitRepository.Tags.CreateTag(options.TagName, options.Objectish) + : _gitRepository.Tags.CreateTagWithMessage(options.TagName, options.Message, options.Objectish); + } +} diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Create/CreateTagOptions.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Create/CreateTagOptions.cs new file mode 100644 index 0000000..5b74dfe --- /dev/null +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Create/CreateTagOptions.cs @@ -0,0 +1,19 @@ +using CreativeCoders.SysConsole.Cli.Parsing; +using JetBrains.Annotations; + +namespace CreativeCoders.GitTool.Cli.Commands.TagGroup.Create; + +[PublicAPI] +public class CreateTagOptions +{ + [OptionValue(0, IsRequired = true)] public string TagName { get; set; } = string.Empty; + + [OptionParameter('p', "push", HelpText = "Push tag after creation")] + public bool PushAfterCreate { get; set; } + + [OptionParameter('m', "message", HelpText = "Message for tag")] + public string? Message { get; set; } + + [OptionParameter('o', "objectish", HelpText = "Object to tag")] + public string? Objectish { get; set; } +} diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Delete/DeleteTagCommand.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Delete/DeleteTagCommand.cs new file mode 100644 index 0000000..219f084 --- /dev/null +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Delete/DeleteTagCommand.cs @@ -0,0 +1,37 @@ +using CreativeCoders.Cli.Core; +using CreativeCoders.Core; +using CreativeCoders.Git.Abstractions; +using CreativeCoders.SysConsole.Core; +using JetBrains.Annotations; +using Spectre.Console; + +namespace CreativeCoders.GitTool.Cli.Commands.TagGroup.Delete; + +[UsedImplicitly] +[CliCommand([TagCommandGroup.Name, "delete"], Description = "Deletes a tag from the repository")] +public class DeleteTagCommand(IAnsiConsole ansiConsole, IGitRepository gitRepository) : ICliCommand +{ + private readonly IAnsiConsole _ansiConsole = Ensure.NotNull(ansiConsole); + + private readonly IGitRepository _gitRepository = Ensure.NotNull(gitRepository); + + public Task ExecuteAsync(DeleteTagOptions options) + { + _ansiConsole.WriteLine($"Deleting tag '{options.TagName}'..."); + + _gitRepository.Tags.DeleteTag(options.TagName); + + _ansiConsole.MarkupLine($"Tag '{options.TagName}' deleted successfully".ToSuccessMarkup()); + + if (options.DeleteOnRemote) + { + _ansiConsole.WriteLine("Deleting tag on remote..."); + + _gitRepository.Tags.DeleteRemoteTag(options.TagName); + + _ansiConsole.MarkupLine($"Tag '{options.TagName}' deleted successfully on remote".ToSuccessMarkup()); + } + + return Task.FromResult(CommandResult.Success); + } +} diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Delete/DeleteTagOptions.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Delete/DeleteTagOptions.cs new file mode 100644 index 0000000..7413433 --- /dev/null +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Delete/DeleteTagOptions.cs @@ -0,0 +1,14 @@ +using CreativeCoders.SysConsole.Cli.Parsing; +using JetBrains.Annotations; + +namespace CreativeCoders.GitTool.Cli.Commands.TagGroup.Delete; + +[PublicAPI] +public class DeleteTagOptions +{ + [OptionValue(0, HelpText = "The name of the tag to delete")] + public string TagName { get; set; } = string.Empty; + + [OptionParameter('r', "remote", HelpText = "Delete tag on remote")] + public bool DeleteOnRemote { get; set; } +} diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Fetch/FetchTagsCommand.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Fetch/FetchTagsCommand.cs new file mode 100644 index 0000000..089e071 --- /dev/null +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Fetch/FetchTagsCommand.cs @@ -0,0 +1,47 @@ +using CreativeCoders.Cli.Core; +using CreativeCoders.Core; +using CreativeCoders.Git.Abstractions; +using CreativeCoders.Git.Abstractions.GitCommands; +using CreativeCoders.SysConsole.Core; +using JetBrains.Annotations; +using Spectre.Console; + +namespace CreativeCoders.GitTool.Cli.Commands.TagGroup.Fetch; + +[UsedImplicitly] +[CliCommand([TagCommandGroup.Name, "fetch"], Description = "Fetch tags from remote repository")] +public class FetchTagsCommand(IAnsiConsole ansiConsole, IGitRepository gitRepository) : ICliCommand +{ + private readonly IAnsiConsole _ansiConsole = Ensure.NotNull(ansiConsole); + + private readonly IGitRepository _gitRepository = Ensure.NotNull(gitRepository); + + public Task ExecuteAsync(FetchTagsOptions options) + { + _ansiConsole.WriteLines("Fetch tags from remote repository", string.Empty); + + var remotes = _gitRepository.Remotes.ToList(); + + if (remotes.Count == 0) + { + _ansiConsole.WriteLine("No remote repository found."); + return Task.FromResult(CommandResult.Success); + } + + var fetchTagsCommand = _gitRepository.Commands.CreateFetchTagsCommand(); + + foreach (var remote in remotes) + { + _ansiConsole.WriteLine($"Fetch tags from remote '{remote.Name}' ({remote.Url})..."); + fetchTagsCommand.Execute(new FetchTagsCommandOptions + { + RemoteName = remote.Name, + Prune = options.Prune + }); + } + + _ansiConsole.MarkupLine("Tags fetched successfully.".ToSuccessMarkup()); + + return Task.FromResult(CommandResult.Success); + } +} diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Fetch/FetchTagsOptions.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Fetch/FetchTagsOptions.cs new file mode 100644 index 0000000..5c51075 --- /dev/null +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/Fetch/FetchTagsOptions.cs @@ -0,0 +1,14 @@ +using CreativeCoders.SysConsole.Cli.Parsing; +using JetBrains.Annotations; + +namespace CreativeCoders.GitTool.Cli.Commands.TagGroup.Fetch; + +[PublicAPI] +public class FetchTagsOptions +{ + [OptionParameter('r', "remote", HelpText = "The remote to fetch tags from")] + public string RemoteName { get; set; } = "origin"; + + [OptionParameter('p', "prune", HelpText = "Prune deleted remote tags")] + public bool Prune { get; set; } = true; +} diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/List/ListTagsCommand.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/List/ListTagsCommand.cs new file mode 100644 index 0000000..c1b3dee --- /dev/null +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/List/ListTagsCommand.cs @@ -0,0 +1,64 @@ +using System.Globalization; +using CreativeCoders.Cli.Core; +using CreativeCoders.Core; +using CreativeCoders.Git.Abstractions; +using CreativeCoders.Git.Abstractions.Tags; +using CreativeCoders.GitTool.Cli.Commands.Shared; +using CreativeCoders.SysConsole.Core; +using JetBrains.Annotations; +using Spectre.Console; + +namespace CreativeCoders.GitTool.Cli.Commands.TagGroup.List; + +[UsedImplicitly] +[CliCommand([TagCommandGroup.Name, "list"], Description = "Lists all tags")] +public class ListTagsCommand(IAnsiConsole ansiConsole, IGitRepository gitRepository) : ICliCommand +{ + private readonly IGitRepository _gitRepository = Ensure.NotNull(gitRepository); + + private readonly IAnsiConsole _ansiConsole = Ensure.NotNull(ansiConsole); + + public Task ExecuteAsync(ListTagsOptions options) + { + _ansiConsole.WriteLines("List all tags:", string.Empty); + + var tags = _gitRepository.Tags.OrderByDescending(x => + x.TargetCommit?.Author.When ?? DateTimeOffset.MinValue); + + options.ShowExtendedInformation + .If(() => ShowExtendedTable(tags)) + .Else(() => ShowSimpleList(tags)); + + return Task.FromResult(CommandResult.Success); + } + + private void ShowSimpleList(IEnumerable tags) + { + foreach (var tag in tags) + { + _ansiConsole.WriteLine($"- {tag.Name.Friendly}"); + } + } + + private void ShowExtendedTable(IEnumerable tags) + { + _ansiConsole.PrintTable(tags, [ + new TableColumnDef(x => x.Name.Friendly, "Name"), + new TableColumnDef(x => + { + var commit = x.TargetCommit; + return commit == null + ? string.Empty + : commit.Author.When.ToString( + $"{CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern} {CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern}"); + }, "Date of commit"), + new TableColumnDef(x => + { + var commit = x.TargetCommit; + return commit == null + ? string.Empty + : $"{commit.Author.Name} ({commit.Author.Email})"; + }, "Committer") + ]); + } +} diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/List/ListTagsOptions.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/List/ListTagsOptions.cs new file mode 100644 index 0000000..9c9a802 --- /dev/null +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/List/ListTagsOptions.cs @@ -0,0 +1,11 @@ +using CreativeCoders.SysConsole.Cli.Parsing; +using JetBrains.Annotations; + +namespace CreativeCoders.GitTool.Cli.Commands.TagGroup.List; + +[PublicAPI] +public class ListTagsOptions +{ + [OptionParameter('x', "extended", HelpText = "Show extended information")] + public bool ShowExtendedInformation { get; set; } +} diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/TagCommandGroup.cs b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/TagCommandGroup.cs new file mode 100644 index 0000000..f8683f6 --- /dev/null +++ b/source/GitTool/CreativeCoders.GitTool.Cli.Commands/TagGroup/TagCommandGroup.cs @@ -0,0 +1,10 @@ +using CreativeCoders.Cli.Core; + +[assembly: CliCommandGroup(["tag"], "Commands for managing tags")] + +namespace CreativeCoders.GitTool.Cli.Commands.TagGroup; + +public static class TagCommandGroup +{ + public const string Name = "tag"; +} diff --git a/source/GitTool/CreativeCoders.GitTool.Cli.GtApp/Program.cs b/source/GitTool/CreativeCoders.GitTool.Cli.GtApp/Program.cs index 51579ac..8f16600 100644 --- a/source/GitTool/CreativeCoders.GitTool.Cli.GtApp/Program.cs +++ b/source/GitTool/CreativeCoders.GitTool.Cli.GtApp/Program.cs @@ -24,6 +24,7 @@ internal static async Task Main(string[] args) { var result = await CliHostBuilder.Create() .ConfigureServices(ConfigureServices) + .PrintFooterText([string.Empty]) .EnableHelp(HelpCommandKind.CommandOrArgument) .ScanAssemblies(typeof(ShowConfigCommand).Assembly) .Build()