public SemanticReleaseNotes Merge(SemanticReleaseNotes previousReleaseNotes)
        {
            var semanticReleases = previousReleaseNotes.Releases
                                   .Where(r => Releases.All(r2 => r.ReleaseName != r2.ReleaseName))
                                   .Select(CreateMergedSemanticRelease);
            var enumerable     = Releases.Select(CreateMergedSemanticRelease);
            var mergedReleases =
                enumerable
                .Union(semanticReleases)
                .ToArray();

            foreach (var semanticRelease in mergedReleases)
            {
                var releaseFromThis     = Releases.SingleOrDefault(r => r.ReleaseName == semanticRelease.ReleaseName);
                var releaseFromPrevious = previousReleaseNotes.Releases.SingleOrDefault(r => r.ReleaseName == semanticRelease.ReleaseName);

                if (releaseFromThis != null)
                {
                    semanticRelease.ReleaseNoteLines.AddRange(releaseFromThis.ReleaseNoteLines);
                }

                if (releaseFromPrevious != null)
                {
                    semanticRelease.ReleaseNoteLines.AddRange(releaseFromPrevious.ReleaseNoteLines);
                }
            }

            return(new SemanticReleaseNotes(mergedReleases, new Categories(categories.AvailableCategories.Union(previousReleaseNotes.categories.AvailableCategories).Distinct().ToArray(), categories.AllLabels)));
        }
        public static SemanticReleaseNotes GenerateReleaseNotes(Context context,
            IRepository gitRepo, IIssueTracker issueTracker, SemanticReleaseNotes previousReleaseNotes,
            Categories categories, TaggedCommit tagToStartFrom, ReleaseInfo currentReleaseInfo)
        {
            var releases = ReleaseFinder.FindReleases(gitRepo, tagToStartFrom, currentReleaseInfo);
            var findIssuesSince =
                IssueStartDateBasedOnPreviousReleaseNotes(gitRepo, previousReleaseNotes)
                ??
                tagToStartFrom.Commit.Author.When;

            var closedIssues = issueTracker.GetClosedIssues(context.IssueTracker, findIssuesSince).ToArray();

            var semanticReleases = (
                from release in releases
                let releaseNoteItems = closedIssues
                    .Where(i => (release.When == null || i.DateClosed < release.When) && (release.PreviousReleaseDate == null || i.DateClosed > release.PreviousReleaseDate))
                    .Select(i => new ReleaseNoteItem(i.Title, i.Id, i.HtmlUrl, i.Labels, i.DateClosed, i.Contributors))
                    .ToList<IReleaseNoteLine>()
                let beginningSha = release.FirstCommit == null ? null : release.FirstCommit.Substring(0, 10)
                let endSha = release.LastCommit == null ? null : release.LastCommit.Substring(0, 10)
                select new SemanticRelease(release.Name, release.When, releaseNoteItems, new ReleaseDiffInfo
                {
                    BeginningSha = beginningSha,
                    EndSha = endSha,
                    //DiffUrlFormat = issueTracker.DiffUrlFormat
                })).ToList();

            return new SemanticReleaseNotes(semanticReleases, categories).Merge(previousReleaseNotes);
        }
        private static DateTimeOffset?IssueStartDateBasedOnPreviousReleaseNotes(IRepository gitRepo,
                                                                                SemanticReleaseNotes previousReleaseNotes)
        {
            var lastGeneratedRelease = previousReleaseNotes.Releases.FirstOrDefault();

            if (lastGeneratedRelease == null)
            {
                return(null);
            }
            var endSha = lastGeneratedRelease.DiffInfo.EndSha;

            if (string.IsNullOrEmpty(endSha))
            {
                lastGeneratedRelease = previousReleaseNotes.Releases.Skip(1).FirstOrDefault();
                if (lastGeneratedRelease != null)
                {
                    endSha = lastGeneratedRelease.DiffInfo.EndSha;
                }
            }

            if (string.IsNullOrEmpty(endSha))
            {
                return(null);
            }
            var commitToStartFrom = gitRepo.Commits.FirstOrDefault(c => c.Sha.StartsWith(endSha));

            if (commitToStartFrom != null)
            {
                return(commitToStartFrom.Author.When);
            }
            return(null);
        }
        public static SemanticReleaseNotes GenerateReleaseNotes(
            IRepository gitRepo, IIssueTracker issueTracker, SemanticReleaseNotes previousReleaseNotes,
            Categories categories, TaggedCommit tagToStartFrom, ReleaseInfo currentReleaseInfo,
            string diffUrlFormat)
        {
            var releases        = ReleaseFinder.FindReleases(gitRepo, tagToStartFrom, currentReleaseInfo);
            var findIssuesSince =
                IssueStartDateBasedOnPreviousReleaseNotes(gitRepo, previousReleaseNotes)
                ??
                tagToStartFrom.Commit.Author.When;

            var closedIssues = issueTracker.GetClosedIssues(findIssuesSince).ToArray();

            var semanticReleases = (
                from release in releases
                let releaseNoteItems = closedIssues
                                       .Where(i => (release.When == null || i.DateClosed < release.When) && (release.PreviousReleaseDate == null || i.DateClosed > release.PreviousReleaseDate))
                                       .Select(i => new ReleaseNoteItem(i.Title, i.Id, i.HtmlUrl, i.Labels, i.DateClosed, i.Contributors))
                                       .ToList <IReleaseNoteLine>()
                                       let beginningSha = release.FirstCommit == null ? null : release.FirstCommit.Substring(0, 10)
                                                          let endSha = release.LastCommit == null ? null : release.LastCommit.Substring(0, 10)
                                                                       select new SemanticRelease(release.Name, release.When, releaseNoteItems, new ReleaseDiffInfo
            {
                BeginningSha = beginningSha,
                EndSha = endSha,
                DiffUrlFormat = diffUrlFormat
            })).ToList();

            return(new SemanticReleaseNotes(semanticReleases, categories).Merge(previousReleaseNotes));
        }
        public async Task <SemanticReleaseNotes> GenerateReleaseNotesAsync(SemanticReleaseNotes releaseNotesToUpdate)
        {
            var           gitRepository  = new Repository(Repository.Discover(_generationParameters.WorkingDirectory));
            IIssueTracker issueTracker   = null;
            var           categories     = new Categories(_generationParameters.Categories, _generationParameters.AllLabels);
            var           tagToStartFrom = _generationParameters.AllTags
                ? gitRepository.GetFirstCommit()
                : gitRepository.GetLastTaggedCommit() ?? gitRepository.GetFirstCommit();
            var currentReleaseInfo = gitRepository.GetCurrentReleaseInfo();

            if (!string.IsNullOrEmpty(_generationParameters.Version))
            {
                currentReleaseInfo.Name = _generationParameters.Version;
                currentReleaseInfo.When = DateTimeOffset.Now;
            }
            else
            {
                currentReleaseInfo.Name = "vNext";
            }

            if (_generationParameters.UseIssueTracker)
            {
                if (_generationParameters.IssueTracker.Type.HasValue)
                {
                    issueTracker = IssueTrackerFactory.CreateIssueTracker(new IssueTrackerSettings(_generationParameters.IssueTracker.Server,
                                                                                                   _generationParameters.IssueTracker.Type.Value)
                    {
                        Project        = _generationParameters.IssueTracker.ProjectId,
                        Authentication = _generationParameters.IssueTracker.Authentication.ToIssueTrackerSettings()
                    });
                }
                else
                {
                    if (!TryRemote(gitRepository, "upstream", _generationParameters, out issueTracker) &&
                        !TryRemote(gitRepository, "origin", _generationParameters, out issueTracker))
                    {
                        throw new Exception("Unable to guess issue tracker through remote, specify issue tracker type on the command line");
                    }
                }
            }


            var releaseNotes = _generationParameters.UseIssueTracker ? await GenerateReleaseNotesAsync(_generationParameters, gitRepository, issueTracker, releaseNotesToUpdate, categories, tagToStartFrom, currentReleaseInfo)
                : await GenerateReleaseNotesAsync(
                _generationParameters, gitRepository,
                releaseNotesToUpdate, categories,
                tagToStartFrom, currentReleaseInfo);

            return(releaseNotes);
        }
        public async Task<SemanticReleaseNotes> GenerateReleaseNotesAsync(SemanticReleaseNotes releaseNotesToUpdate)
        {
            var gitRepository = new Repository(Repository.Discover(_generationParameters.WorkingDirectory));
            IIssueTracker issueTracker;
            if (_generationParameters.IssueTracker.Type.HasValue)
            {
                issueTracker = IssueTrackerFactory.CreateIssueTracker(new IssueTrackerSettings(_generationParameters.IssueTracker.Server,
                        _generationParameters.IssueTracker.Type.Value)
                    {
                        Project = _generationParameters.IssueTracker.ProjectId,
                        Authentication = _generationParameters.IssueTracker.Authentication.ToIssueTrackerSettings()
                    });
            }
            else
            {
                if (!TryRemote(gitRepository, "upstream", _generationParameters, out issueTracker) &&
                    !TryRemote(gitRepository, "origin", _generationParameters, out issueTracker))
                {
                    throw new Exception("Unable to guess issue tracker through remote, specify issue tracker type on the command line");
                }
            }

            var categories = new Categories(_generationParameters.Categories, _generationParameters.AllLabels);
            var tagToStartFrom = _generationParameters.AllTags
                ? gitRepository.GetFirstCommit()
                : gitRepository.GetLastTaggedCommit() ?? gitRepository.GetFirstCommit();
            var currentReleaseInfo = gitRepository.GetCurrentReleaseInfo();
            if (!string.IsNullOrEmpty(_generationParameters.Version))
            {
                currentReleaseInfo.Name = _generationParameters.Version;
                currentReleaseInfo.When = DateTimeOffset.Now;
            }
            else
            {
                currentReleaseInfo.Name = "vNext";
            }

            var releaseNotes = await GenerateReleaseNotesAsync(
                _generationParameters, gitRepository, issueTracker,
                releaseNotesToUpdate, categories,
                tagToStartFrom, currentReleaseInfo);

            return releaseNotes;
        }
        public void WriteReleaseNotes(GitReleaseNotesArguments arguments, SemanticReleaseNotes releaseNotes)
        {
            var builder = new StringBuilder();
            var categories = arguments.Categories == null ? _categories : _categories.Concat(arguments.Categories.Split(',')).ToArray();
            foreach (var releaseNoteItem in releaseNotes.ReleaseNoteItems)
            {
                var taggedCategory = releaseNoteItem.Tags
                    .FirstOrDefault(t => categories.Any(c => c.Equals(t, StringComparison.InvariantCultureIgnoreCase)));
                var title = releaseNoteItem.Title;
                var issueNumber = releaseNoteItem.IssueNumber;
                var htmlUrl = releaseNoteItem.HtmlUrl;
                if ("bug".Equals(taggedCategory, StringComparison.InvariantCultureIgnoreCase))
                    taggedCategory = "fix";
                var category = taggedCategory == null ? null : string.Format(" +{0}", taggedCategory.Replace(" ", "-"));
                var item = string.Format(" - {0} [{1}]({2}){3}", title, issueNumber, htmlUrl, category);
                builder.AppendLine(item);
            }

            var outputFile = Path.IsPathRooted(arguments.OutputFile) ? arguments.OutputFile : Path.Combine(_workingDirectory, arguments.OutputFile);
            _fileSystem.WriteAllText(outputFile, builder.ToString());
        }
        public static Task <SemanticReleaseNotes> GenerateReleaseNotesAsync(ReleaseNotesGenerationParameters generationParameters,
                                                                            IRepository gitRepo, SemanticReleaseNotes previousReleaseNotes,
                                                                            Categories categories, TaggedCommit tagToStartFrom, ReleaseInfo currentReleaseInfo)
        {
            var issuenumberregex = new Regex(@"#\s?(\d+)", RegexOptions.Compiled);
            var semanticReleases = new Dictionary <string, SemanticRelease>();
            var releases         = ReleaseFinder.FindReleases(gitRepo, tagToStartFrom, currentReleaseInfo);

            foreach (var release in releases)
            {
                if (!semanticReleases.ContainsKey(release.Name))
                {
                    var beginningSha = release.FirstCommit != null?release.FirstCommit.Substring(0, 10) : null;

                    var endSha = release.LastCommit != null?release.LastCommit.Substring(0, 10) : null;

                    semanticReleases.Add(release.Name, new SemanticRelease(release.Name, release.When, new ReleaseDiffInfo
                    {
                        BeginningSha = beginningSha,
                        EndSha       = endSha,
                        // TODO DiffUrlFormat = context.Repository.DiffUrlFormat
                    }));
                }

                var semanticRelease = semanticReleases[release.Name];
                var commits         = gitRepo.Commits.Where(x => x.Author.When >= release.PreviousReleaseDate && x.Author.When <= release.When);
                foreach (Commit commit in commits)
                {
                    var match           = issuenumberregex.Match(commit.Message);
                    var releaseNoteItem = new ReleaseNoteItem(match.Success ? commit.Message.Replace(match.Groups[0].Value, "") :commit.Message, match.Success ? match.Groups[0].Value : string.Empty, match.Success? string.Format(generationParameters.IssueTracker.Server, match.Groups[0].Value): string.Empty, null, commit.Author.When, new[] { new Contributor(commit.Author.Name, commit.Author.Email, string.Empty) });

                    semanticRelease.ReleaseNoteLines.Add(releaseNoteItem);
                }
            }
            var semanticReleaseNotes = new SemanticReleaseNotes(semanticReleases.Values, categories);
            var mergedReleaseNotes   = semanticReleaseNotes.Merge(previousReleaseNotes);

            return(Task.FromResult(mergedReleaseNotes));
        }
        private static DateTimeOffset? IssueStartDateBasedOnPreviousReleaseNotes(IRepository gitRepo,
            SemanticReleaseNotes previousReleaseNotes)
        {
            var lastGeneratedRelease = previousReleaseNotes.Releases.FirstOrDefault();
            if (lastGeneratedRelease == null) return null;
            var endSha = lastGeneratedRelease.DiffInfo.EndSha;
            if (string.IsNullOrEmpty(endSha))
            {
                lastGeneratedRelease = previousReleaseNotes.Releases.Skip(1).FirstOrDefault();
                if (lastGeneratedRelease != null)
                {
                    endSha = lastGeneratedRelease.DiffInfo.EndSha;
                }
            }

            if (string.IsNullOrEmpty(endSha)) return null;
            var commitToStartFrom = gitRepo.Commits.FirstOrDefault(c => c.Sha.StartsWith(endSha));
            if (commitToStartFrom != null)
            {
                return commitToStartFrom.Author.When;
            }
            return null;
        }
Пример #10
0
        public static int Main(string[] args)
        {
            GitReleaseNotesEnvironment.Log = new ConsoleLog();

            var modelBindingDefinition = Configuration.Configure <GitReleaseNotesArguments>();

            if (!args.Any() || args.Any(a => a == "/?" || a == "?" || a.Equals("/help", StringComparison.InvariantCultureIgnoreCase)))
            {
                ShowHelp(modelBindingDefinition);

                return(0);
            }

            var exitCode = 0;

            var arguments = modelBindingDefinition.CreateAndBind(args);

            if (string.IsNullOrEmpty(arguments.OutputFile))
            {
                ShowHelp(modelBindingDefinition);
                return(1);
            }
            var context = arguments.ToContext();

            //if (!context.Validate())
            //{
            //    return -1;
            //}

            try
            {
                var    fileSystem           = new FileSystem.FileSystem();
                var    releaseFileWriter    = new ReleaseFileWriter(fileSystem);
                string outputFile           = null;
                var    previousReleaseNotes = new SemanticReleaseNotes();

                var outputPath      = context.WorkingDirectory;
                var outputDirectory = new DirectoryInfo(outputPath);
                if (outputDirectory.Name == ".git")
                {
                    outputPath = outputDirectory.Parent.FullName;
                }

                if (!string.IsNullOrEmpty(arguments.OutputFile))
                {
                    outputFile = Path.IsPathRooted(arguments.OutputFile)
                        ? arguments.OutputFile
                        : Path.Combine(outputPath, arguments.OutputFile);
                    previousReleaseNotes = new ReleaseNotesFileReader(fileSystem, outputPath).ReadPreviousReleaseNotes(outputFile);
                }

                var releaseNotesGenerator = new ReleaseNotesGenerator(context);
                var releaseNotes          = releaseNotesGenerator.GenerateReleaseNotesAsync(previousReleaseNotes).Result;

                var releaseNotesOutput = releaseNotes.ToString();
                releaseFileWriter.OutputReleaseNotesFile(releaseNotesOutput, outputFile);

                Log.WriteLine("Done");
            }
            catch (GitReleaseNotesException ex)
            {
                exitCode = -1;
                Log.WriteLine("An expected error occurred: {0}", ex.Message);
            }
            catch (Exception ex)
            {
                exitCode = -2;
                Log.WriteLine("An unexpected error occurred: {0}", ex.Message);
            }

            if (Debugger.IsAttached)
            {
                Console.WriteLine("Press any key to continue");
                Console.ReadKey();
            }

            return(exitCode);
        }
Пример #11
0
        static int Main(string[] args)
        {
            GitReleaseNotesEnvironment.Log = new ConsoleLog();

            var modelBindingDefinition = Configuration.Configure<GitReleaseNotesArguments>();

            if (args.Any(a => a == "/?" || a == "?" || a.Equals("/help", StringComparison.InvariantCultureIgnoreCase)))
            {
                ShowHelp(modelBindingDefinition);

                return 0;
            }

            var exitCode = 0;

            var arguments = modelBindingDefinition.CreateAndBind(args);
            if (string.IsNullOrEmpty(arguments.OutputFile))
            {
                ShowHelp(modelBindingDefinition);
                return 1;
            }
            var context = arguments.ToContext();
            //if (!context.Validate())
            //{
            //    return -1;
            //}

            try
            {
                var fileSystem = new FileSystem.FileSystem();
                var releaseFileWriter = new ReleaseFileWriter(fileSystem);
                string outputFile = null;
                var previousReleaseNotes = new SemanticReleaseNotes();

                var outputPath = context.WorkingDirectory;
                var outputDirectory = new DirectoryInfo(outputPath);
                if (outputDirectory.Name == ".git")
                {
                    outputPath = outputDirectory.Parent.FullName;
                }

                if (!string.IsNullOrEmpty(arguments.OutputFile))
                {
                    outputFile = Path.IsPathRooted(arguments.OutputFile)
                        ? arguments.OutputFile
                        : Path.Combine(outputPath, arguments.OutputFile);
                    previousReleaseNotes = new ReleaseNotesFileReader(fileSystem, outputPath).ReadPreviousReleaseNotes(outputFile);
                }

                var releaseNotesGenerator = new ReleaseNotesGenerator(context);
                var releaseNotes = releaseNotesGenerator.GenerateReleaseNotesAsync(previousReleaseNotes).Result;

                var releaseNotesOutput = releaseNotes.ToString();
                releaseFileWriter.OutputReleaseNotesFile(releaseNotesOutput, outputFile);

                Log.WriteLine("Done");
            }
            catch (GitReleaseNotesException ex)
            {
                exitCode = -1;
                Log.WriteLine("An expected error occurred: {0}", ex.Message);
            }
            catch (Exception ex)
            {
                exitCode = -2;
                Log.WriteLine("An unexpected error occurred: {0}", ex.Message);
            }

            if (Debugger.IsAttached)
            {
                Console.WriteLine("Press any key to continue");
                Console.ReadKey();
            }

            return exitCode;
        }
        public SemanticReleaseNotes GenerateReleaseNotes()
        {
            var context = _context;

            using (var gitRepoContext = GetRepository(context, _fileSystem))
            {
                // Remote repo's require some additional preparation before first use.
                if (gitRepoContext.IsRemote)
                {
                    gitRepoContext.PrepareRemoteRepoForUse(context.Repository.Branch);
                    if (!string.IsNullOrWhiteSpace(context.OutputFile))
                    {
                        gitRepoContext.CheckoutFilesIfExist(context.OutputFile);
                    }
                }

                var gitRepo = gitRepoContext.Repository;

                if (context.IssueTracker == null)
                {
                    // TODO: Write auto detection mechanism which is better than this
                    throw new GitReleaseNotesException("Feature to automatically detect issue tracker must be written");
                    //var firstOrDefault = _issueTrackers.FirstOrDefault(i => i.Value.RemotePresentWhichMatches);
                    //if (firstOrDefault.Value != null)
                    //{
                    //    issueTracker = firstOrDefault.Value;
                    //}
                }

                var issueTracker = _issueTrackerFactory.CreateIssueTracker(context, gitRepo);
                if (issueTracker == null)
                {
                    throw new GitReleaseNotesException("Failed to create issue tracker from context, cannot continue");
                }

                var releaseFileWriter = new ReleaseFileWriter(_fileSystem);
                string outputFile = null;
                var previousReleaseNotes = new SemanticReleaseNotes();

                var outputPath = gitRepo.Info.Path;
                var outputDirectory = new DirectoryInfo(outputPath);
                if (outputDirectory.Name == ".git")
                {
                    outputPath = outputDirectory.Parent.FullName;
                }

                if (!string.IsNullOrEmpty(context.OutputFile))
                {
                    outputFile = Path.IsPathRooted(context.OutputFile)
                        ? context.OutputFile
                        : Path.Combine(outputPath, context.OutputFile);
                    previousReleaseNotes = new ReleaseNotesFileReader(_fileSystem, outputPath).ReadPreviousReleaseNotes(outputFile);
                }

                var categories = new Categories(context.Categories, context.AllLabels);
                var tagToStartFrom = context.AllTags
                    ? GitRepositoryInfoFinder.GetFirstCommit(gitRepo)
                    : GitRepositoryInfoFinder.GetLastTaggedCommit(gitRepo) ?? GitRepositoryInfoFinder.GetFirstCommit(gitRepo);
                var currentReleaseInfo = GitRepositoryInfoFinder.GetCurrentReleaseInfo(gitRepo);
                if (!string.IsNullOrEmpty(context.Version))
                {
                    currentReleaseInfo.Name = context.Version;
                    currentReleaseInfo.When = DateTimeOffset.Now;
                }

                var releaseNotes = GenerateReleaseNotes(
                    context, gitRepo, issueTracker,
                    previousReleaseNotes, categories,
                    tagToStartFrom, currentReleaseInfo);

                var releaseNotesOutput = releaseNotes.ToString();
                releaseFileWriter.OutputReleaseNotesFile(releaseNotesOutput, outputFile);

                return releaseNotes;
            }
        }
Пример #13
0
        private static int GenerateReleaseNotes(string[] args)
        {
            var modelBindingDefinition = Args.Configuration.Configure<GitReleaseNotesArguments>();

            if (args.Any(a => a == "/?" || a == "?" || a.Equals("/help", StringComparison.InvariantCultureIgnoreCase)))
            {
                var help = new HelpProvider().GenerateModelHelp(modelBindingDefinition);
                var f = new ConsoleHelpFormatter();
                f.WriteHelp(help, Console.Out);

                return 0;
            }

            var arguments = modelBindingDefinition.CreateAndBind(args);

            if (!ArgumentVerifier.VerifyArguments(arguments))
            {
                return 1;
            }

            var workingDirectory = arguments.WorkingDirectory ?? Directory.GetCurrentDirectory();

            var gitDirectory = GitDirFinder.TreeWalkForGitDir(workingDirectory);
            if (string.IsNullOrEmpty(gitDirectory))
            {
                throw new Exception("Failed to find .git directory.");
            }

            Console.WriteLine("Git directory found at {0}", gitDirectory);

            var repositoryRoot = Directory.GetParent(gitDirectory).FullName;

            var gitRepo = new Repository(gitDirectory);

            CreateIssueTrackers(gitRepo, arguments);

            IIssueTracker issueTracker = null;
            if (arguments.IssueTracker == null)
            {
                var firstOrDefault = _issueTrackers.FirstOrDefault(i => i.Value.RemotePresentWhichMatches);
                if (firstOrDefault.Value != null)
                    issueTracker = firstOrDefault.Value;
            }
            if (issueTracker == null)
            {
                if (!_issueTrackers.ContainsKey(arguments.IssueTracker.Value))
                    throw new Exception(string.Format("{0} is not a known issue tracker", arguments.IssueTracker.Value));

                issueTracker = _issueTrackers[arguments.IssueTracker.Value];
            }
            if (!issueTracker.VerifyArgumentsAndWriteErrorsToConsole())
            {
                return 1;
            }

            var fileSystem = new FileSystem.FileSystem();
            var releaseFileWriter = new ReleaseFileWriter(fileSystem);
            string outputFile = null;
            var previousReleaseNotes = new SemanticReleaseNotes();
            if (!string.IsNullOrEmpty(arguments.OutputFile))
            {
                outputFile = Path.IsPathRooted(arguments.OutputFile)
                    ? arguments.OutputFile
                    : Path.Combine(repositoryRoot, arguments.OutputFile);
                previousReleaseNotes = new ReleaseNotesFileReader(fileSystem, repositoryRoot).ReadPreviousReleaseNotes(outputFile);
            }

            var categories = arguments.Categories == null ? Categories : Categories.Concat(arguments.Categories.Split(',')).ToArray();
            TaggedCommit tagToStartFrom = arguments.AllTags
                ? GitRepositoryInfoFinder.GetFirstCommit(gitRepo)
                : GitRepositoryInfoFinder.GetLastTaggedCommit(gitRepo) ?? GitRepositoryInfoFinder.GetFirstCommit(gitRepo);
            var currentReleaseInfo = GitRepositoryInfoFinder.GetCurrentReleaseInfo(gitRepo);
            if (!string.IsNullOrEmpty(arguments.Version))
            {
                currentReleaseInfo.Name = arguments.Version;
                currentReleaseInfo.When = DateTimeOffset.Now;
            }
            var releaseNotes = ReleaseNotesGenerator.GenerateReleaseNotes(
                gitRepo, issueTracker,
                previousReleaseNotes, categories,
                tagToStartFrom, currentReleaseInfo,
                issueTracker.DiffUrlFormat);

            var releaseNotesOutput = releaseNotes.ToString();
            releaseFileWriter.OutputReleaseNotesFile(releaseNotesOutput, outputFile);

            PublishReleaseIfNeeded(releaseNotesOutput, arguments, issueTracker);
            return 0;
        }
        public SemanticReleaseNotes Merge(SemanticReleaseNotes previousReleaseNotes)
        {
            var semanticReleases = previousReleaseNotes.Releases
                .Where(r => Releases.All(r2 => r.ReleaseName != r2.ReleaseName))
                .Select(CreateMergedSemanticRelease);
            var enumerable = Releases.Select(CreateMergedSemanticRelease);
            var mergedReleases =
                enumerable
                .Union(semanticReleases)
                .ToArray();

            foreach (var semanticRelease in mergedReleases)
            {
                var releaseFromThis = Releases.SingleOrDefault(r => r.ReleaseName == semanticRelease.ReleaseName);
                var releaseFromPrevious = previousReleaseNotes.Releases.SingleOrDefault(r => r.ReleaseName == semanticRelease.ReleaseName);

                if (releaseFromThis != null)
                {
                    semanticRelease.ReleaseNoteLines.AddRange(releaseFromThis.ReleaseNoteLines);
                }

                if (releaseFromPrevious != null)
                {
                    semanticRelease.ReleaseNoteLines.AddRange(releaseFromPrevious.ReleaseNoteLines);
                }
            }

            return new SemanticReleaseNotes(mergedReleases, new Categories(categories.AvailableCategories.Union(previousReleaseNotes.categories.AvailableCategories).Distinct().ToArray(), categories.AllLabels));
        }
        public static SemanticReleaseNotes GenerateReleaseNotes(Context context)
        {
            using (var gitRepoContext = GetRepository(context))
            {
                // Remote repo's require some additional preparation before first use.
                if (gitRepoContext.IsRemote)
                {
                    gitRepoContext.PrepareRemoteRepoForUse(context.Repository.Branch);
                    if (!string.IsNullOrWhiteSpace(context.OutputFile))
                    {
                        gitRepoContext.CheckoutFilesIfExist(context.OutputFile);
                    }
                }

                var gitRepo = gitRepoContext.Repository;

                CreateIssueTrackers(gitRepo, context);

                IIssueTracker issueTracker = null;
                if (context.IssueTracker == null)
                {
                    var firstOrDefault = _issueTrackers.FirstOrDefault(i => i.Value.RemotePresentWhichMatches);
                    if (firstOrDefault.Value != null)
                    {
                        issueTracker = firstOrDefault.Value;
                    }
                }

                if (issueTracker == null)
                {
                    if (!_issueTrackers.ContainsKey(context.IssueTracker.Value))
                    {
                        throw new GitReleaseNotesException("{0} is not a known issue tracker", context.IssueTracker.Value);
                    }

                    issueTracker = _issueTrackers[context.IssueTracker.Value];
                }

                if (!issueTracker.VerifyArgumentsAndWriteErrorsToLog())
                {
                    throw new GitReleaseNotesException("Argument verification failed");
                }

                var    fileSystem           = new FileSystem.FileSystem();
                var    releaseFileWriter    = new ReleaseFileWriter(fileSystem);
                string outputFile           = null;
                var    previousReleaseNotes = new SemanticReleaseNotes();

                var outputPath      = gitRepo.Info.Path;
                var outputDirectory = new DirectoryInfo(outputPath);
                if (outputDirectory.Name == ".git")
                {
                    outputPath = outputDirectory.Parent.FullName;
                }

                if (!string.IsNullOrEmpty(context.OutputFile))
                {
                    outputFile = Path.IsPathRooted(context.OutputFile)
                        ? context.OutputFile
                        : Path.Combine(outputPath, context.OutputFile);
                    previousReleaseNotes = new ReleaseNotesFileReader(fileSystem, outputPath).ReadPreviousReleaseNotes(outputFile);
                }

                var categories     = new Categories(context.Categories, context.AllLabels);
                var tagToStartFrom = context.AllTags
                    ? GitRepositoryInfoFinder.GetFirstCommit(gitRepo)
                    : GitRepositoryInfoFinder.GetLastTaggedCommit(gitRepo) ?? GitRepositoryInfoFinder.GetFirstCommit(gitRepo);
                var currentReleaseInfo = GitRepositoryInfoFinder.GetCurrentReleaseInfo(gitRepo);
                if (!string.IsNullOrEmpty(context.Version))
                {
                    currentReleaseInfo.Name = context.Version;
                    currentReleaseInfo.When = DateTimeOffset.Now;
                }

                var releaseNotes = GenerateReleaseNotes(
                    gitRepo, issueTracker,
                    previousReleaseNotes, categories,
                    tagToStartFrom, currentReleaseInfo,
                    issueTracker.DiffUrlFormat);

                var releaseNotesOutput = releaseNotes.ToString();
                releaseFileWriter.OutputReleaseNotesFile(releaseNotesOutput, outputFile);

                return(releaseNotes);
            }
        }
Пример #16
0
        private static int GenerateReleaseNotes(string[] args)
        {
            var modelBindingDefinition = Args.Configuration.Configure <GitReleaseNotesArguments>();

            if (args.Any(a => a == "/?" || a == "?" || a.Equals("/help", StringComparison.InvariantCultureIgnoreCase)))
            {
                var help = new HelpProvider().GenerateModelHelp(modelBindingDefinition);
                var f    = new ConsoleHelpFormatter();
                f.WriteHelp(help, Console.Out);

                return(0);
            }

            var arguments = modelBindingDefinition.CreateAndBind(args);

            if (!ArgumentVerifier.VerifyArguments(arguments))
            {
                return(1);
            }

            var workingDirectory = arguments.WorkingDirectory ?? Directory.GetCurrentDirectory();

            var gitDirectory = GitDirFinder.TreeWalkForGitDir(workingDirectory);

            if (string.IsNullOrEmpty(gitDirectory))
            {
                throw new Exception("Failed to find .git directory.");
            }

            Console.WriteLine("Git directory found at {0}", gitDirectory);

            var repositoryRoot = Directory.GetParent(gitDirectory).FullName;

            var gitRepo = new Repository(gitDirectory);

            CreateIssueTrackers(gitRepo, arguments);

            IIssueTracker issueTracker = null;

            if (arguments.IssueTracker == null)
            {
                var firstOrDefault = _issueTrackers.FirstOrDefault(i => i.Value.RemotePresentWhichMatches);
                if (firstOrDefault.Value != null)
                {
                    issueTracker = firstOrDefault.Value;
                }
            }
            if (issueTracker == null)
            {
                if (!_issueTrackers.ContainsKey(arguments.IssueTracker.Value))
                {
                    throw new Exception(string.Format("{0} is not a known issue tracker", arguments.IssueTracker.Value));
                }

                issueTracker = _issueTrackers[arguments.IssueTracker.Value];
            }
            if (!issueTracker.VerifyArgumentsAndWriteErrorsToConsole())
            {
                return(1);
            }

            var    fileSystem           = new FileSystem.FileSystem();
            var    releaseFileWriter    = new ReleaseFileWriter(fileSystem);
            string outputFile           = null;
            var    previousReleaseNotes = new SemanticReleaseNotes();

            if (!string.IsNullOrEmpty(arguments.OutputFile))
            {
                outputFile = Path.IsPathRooted(arguments.OutputFile)
                    ? arguments.OutputFile
                    : Path.Combine(repositoryRoot, arguments.OutputFile);
                previousReleaseNotes = new ReleaseNotesFileReader(fileSystem, repositoryRoot).ReadPreviousReleaseNotes(outputFile);
            }

            var          categories     = arguments.Categories == null ? Categories : Categories.Concat(arguments.Categories.Split(',')).ToArray();
            TaggedCommit tagToStartFrom = arguments.AllTags
                ? GitRepositoryInfoFinder.GetFirstCommit(gitRepo)
                : GitRepositoryInfoFinder.GetLastTaggedCommit(gitRepo) ?? GitRepositoryInfoFinder.GetFirstCommit(gitRepo);
            var currentReleaseInfo = GitRepositoryInfoFinder.GetCurrentReleaseInfo(gitRepo);

            if (!string.IsNullOrEmpty(arguments.Version))
            {
                currentReleaseInfo.Name = arguments.Version;
                currentReleaseInfo.When = DateTimeOffset.Now;
            }
            var releaseNotes = ReleaseNotesGenerator.GenerateReleaseNotes(
                gitRepo, issueTracker,
                previousReleaseNotes, categories,
                tagToStartFrom, currentReleaseInfo,
                issueTracker.DiffUrlFormat);

            var releaseNotesOutput = releaseNotes.ToString();

            releaseFileWriter.OutputReleaseNotesFile(releaseNotesOutput, outputFile);

            return(0);
        }
        public static async Task <SemanticReleaseNotes> GenerateReleaseNotesAsync(ReleaseNotesGenerationParameters generationParameters,
                                                                                  IRepository gitRepo, IIssueTracker issueTracker, SemanticReleaseNotes previousReleaseNotes,
                                                                                  Categories categories, TaggedCommit tagToStartFrom, ReleaseInfo currentReleaseInfo)
        {
            var releases = ReleaseFinder.FindReleases(gitRepo, tagToStartFrom, currentReleaseInfo);

            var findIssuesSince =
                IssueStartDateBasedOnPreviousReleaseNotes(gitRepo, previousReleaseNotes)
                ??
                tagToStartFrom.Commit.Author.When;

            var filter = new IssueTrackerFilter
            {
                Since       = findIssuesSince,
                IncludeOpen = false
            };

            var closedIssues = (await issueTracker.GetIssuesAsync(filter)).ToArray();

            // As discussed here: https://github.com/GitTools/GitReleaseNotes/issues/85

            var semanticReleases = new Dictionary <string, SemanticRelease>();

            foreach (var issue in closedIssues)
            {
                // 1) Include all issues from the issue tracker that are assigned to this release
                foreach (var fixVersion in issue.FixVersions)
                {
                    if (!fixVersion.IsReleased)
                    {
                        continue;
                    }

                    if (!semanticReleases.ContainsKey(fixVersion.Name))
                    {
                        semanticReleases.Add(fixVersion.Name, new SemanticRelease(fixVersion.Name, fixVersion.ReleaseDate));
                    }

                    var semanticRelease = semanticReleases[fixVersion.Name];

                    var releaseNoteItem = new ReleaseNoteItem(issue.Title, issue.Id, issue.Url, issue.Labels,
                                                              issue.DateClosed, new Contributor[] { /*TODO: implement*/ });

                    semanticRelease.ReleaseNoteLines.Add(releaseNoteItem);
                }

                // 2) Get closed issues from the issue tracker that have no fixversion but are closed between the last release and this release
                if (issue.FixVersions.Count == 0)
                {
                    foreach (var release in releases)
                    {
                        if (issue.DateClosed.HasValue &&
                            issue.DateClosed.Value > release.PreviousReleaseDate &&
                            (release.When == null || issue.DateClosed <= release.When))
                        {
                            if (!semanticReleases.ContainsKey(release.Name))
                            {
                                var beginningSha = release.FirstCommit != null?release.FirstCommit.Substring(0, 10) : null;

                                var endSha = release.LastCommit != null?release.LastCommit.Substring(0, 10) : null;

                                semanticReleases.Add(release.Name, new SemanticRelease(release.Name, release.When, new ReleaseDiffInfo
                                {
                                    BeginningSha = beginningSha,
                                    EndSha       = endSha,
                                    // TODO DiffUrlFormat = context.Repository.DiffUrlFormat
                                }));
                            }

                            var semanticRelease = semanticReleases[release.Name];

                            var releaseNoteItem = new ReleaseNoteItem(issue.Title, issue.Id, issue.Url, issue.Labels,
                                                                      issue.DateClosed, issue.Contributors);

                            semanticRelease.ReleaseNoteLines.Add(releaseNoteItem);
                        }
                    }
                }
            }

            // 3) Remove any duplicates
            foreach (var semanticRelease in semanticReleases.Values)
            {
                var handledIssues = new HashSet <string>();

                for (var i = 0; i < semanticRelease.ReleaseNoteLines.Count; i++)
                {
                    var releaseNoteLine = semanticRelease.ReleaseNoteLines[i] as ReleaseNoteItem;
                    if (releaseNoteLine == null)
                    {
                        continue;
                    }

                    if (handledIssues.Contains(releaseNoteLine.IssueNumber))
                    {
                        semanticRelease.ReleaseNoteLines.RemoveAt(i--);
                        continue;
                    }

                    handledIssues.Add(releaseNoteLine.IssueNumber);
                }
            }

            var semanticReleaseNotes = new SemanticReleaseNotes(semanticReleases.Values, categories);
            var mergedReleaseNotes   = semanticReleaseNotes.Merge(previousReleaseNotes);

            return(mergedReleaseNotes);
        }
        public static async Task<SemanticReleaseNotes> GenerateReleaseNotesAsync(ReleaseNotesGenerationParameters generationParameters,
            IRepository gitRepo, IIssueTracker issueTracker, SemanticReleaseNotes previousReleaseNotes,
            Categories categories, TaggedCommit tagToStartFrom, ReleaseInfo currentReleaseInfo)
        {
            var releases = ReleaseFinder.FindReleases(gitRepo, tagToStartFrom, currentReleaseInfo);

            var findIssuesSince =
                IssueStartDateBasedOnPreviousReleaseNotes(gitRepo, previousReleaseNotes)
                ??
                tagToStartFrom.Commit.Author.When;

            var filter = new IssueTrackerFilter
            {
                Since = findIssuesSince,
                IncludeOpen = false
            };

            var closedIssues = (await issueTracker.GetIssuesAsync(filter)).ToArray();

            // As discussed here: https://github.com/GitTools/GitReleaseNotes/issues/85

            var semanticReleases = new Dictionary<string, SemanticRelease>();

            foreach (var issue in closedIssues)
            {
                // 1) Include all issues from the issue tracker that are assigned to this release
                foreach (var fixVersion in issue.FixVersions)
                {
                    if (!fixVersion.IsReleased)
                    {
                        continue;
                    }

                    if (!semanticReleases.ContainsKey(fixVersion.Name))
                    {
                        semanticReleases.Add(fixVersion.Name, new SemanticRelease(fixVersion.Name, fixVersion.ReleaseDate));
                    }

                    var semanticRelease = semanticReleases[fixVersion.Name];

                    var releaseNoteItem = new ReleaseNoteItem(issue.Title, issue.Id, issue.Url, issue.Labels,
                        issue.DateClosed, new Contributor[] { /*TODO: implement*/ });

                    semanticRelease.ReleaseNoteLines.Add(releaseNoteItem);
                }

                // 2) Get closed issues from the issue tracker that have no fixversion but are closed between the last release and this release
                if (issue.FixVersions.Count == 0)
                {
                    foreach (var release in releases)
                    {
                        if (issue.DateClosed.HasValue &&
                            issue.DateClosed.Value > release.PreviousReleaseDate &&
                            (release.When == null || issue.DateClosed <= release.When))
                        {
                            if (!semanticReleases.ContainsKey(release.Name))
                            {
                                var beginningSha = release.FirstCommit != null ? release.FirstCommit.Substring(0, 10) : null;
                                var endSha = release.LastCommit != null ? release.LastCommit.Substring(0, 10) : null;

                                semanticReleases.Add(release.Name, new SemanticRelease(release.Name, release.When, new ReleaseDiffInfo
                                {
                                    BeginningSha = beginningSha,
                                    EndSha = endSha,
                                    // TODO DiffUrlFormat = context.Repository.DiffUrlFormat
                                }));
                            }

                            var semanticRelease = semanticReleases[release.Name];

                            var releaseNoteItem = new ReleaseNoteItem(issue.Title, issue.Id, issue.Url, issue.Labels,
                                issue.DateClosed, issue.Contributors);

                            semanticRelease.ReleaseNoteLines.Add(releaseNoteItem);
                        }
                    }
                }
            }

            // 3) Remove any duplicates
            foreach (var semanticRelease in semanticReleases.Values)
            {
                var handledIssues = new HashSet<string>();

                for (var i = 0; i < semanticRelease.ReleaseNoteLines.Count; i++)
                {
                    var releaseNoteLine = semanticRelease.ReleaseNoteLines[i] as ReleaseNoteItem;
                    if (releaseNoteLine == null)
                    {
                        continue;
                    }

                    if (handledIssues.Contains(releaseNoteLine.IssueNumber))
                    {
                        semanticRelease.ReleaseNoteLines.RemoveAt(i--);
                        continue;
                    }

                    handledIssues.Add(releaseNoteLine.IssueNumber);
                }
            }

            var semanticReleaseNotes = new SemanticReleaseNotes(semanticReleases.Values, categories);
            var mergedReleaseNotes = semanticReleaseNotes.Merge(previousReleaseNotes);
            return mergedReleaseNotes;
        }