Exemple #1
0
 private static void UpdateVersionNumbers(ConfigFile config, IEnumerable<ProjectChangeInfo> changes)
 {
     foreach (ProjectChangeInfo changeInfo in changes)
     {
         switch (changeInfo.UpdateMode)
         {
             default:
             case UpdateMode.None: continue;
             case UpdateMode.Major:
                 changeInfo.Project.NuSpecVersion = new Version(
                     changeInfo.Project.NuSpecVersion.Major + 1,
                     0,
                     0);
                 break;
             case UpdateMode.Minor:
                 changeInfo.Project.NuSpecVersion = new Version(
                     changeInfo.Project.NuSpecVersion.Major,
                     changeInfo.Project.NuSpecVersion.Minor + 1,
                     0);
                 break;
             case UpdateMode.Patch:
                 changeInfo.Project.NuSpecVersion = new Version(
                     changeInfo.Project.NuSpecVersion.Major,
                     changeInfo.Project.NuSpecVersion.Minor,
                     changeInfo.Project.NuSpecVersion.Build + 1);
                 break;
         }
     }
 }
Exemple #2
0
 private static string SearchGitPath(ConfigFile config)
 {
     foreach (string dir in config.GitSearchPaths)
     {
         string localGitPath = Directory.EnumerateFiles(dir, "git.exe", SearchOption.TopDirectoryOnly).FirstOrDefault();
         if (localGitPath != null)
             return localGitPath;
     }
     return null;
 }
Exemple #3
0
 private static void UpdateConfigPaths(ConfigFile config, string relativeBaseDir)
 {
     string localAppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
     config.NuSpecRootDir = GetAbsoluteConfigPath(relativeBaseDir, config.NuSpecRootDir);
     config.SolutionPath = GetAbsoluteConfigPath(relativeBaseDir, config.SolutionPath);
     for (int i = 0; i < config.GitSearchPaths.Count; i++)
     {
         config.GitSearchPaths[i] = config.GitSearchPaths[i].Replace("{LocalAppData}", localAppDataPath);
         config.GitSearchPaths[i] = GetAbsoluteConfigPath(relativeBaseDir, config.GitSearchPaths[i]);
     }
 }
Exemple #4
0
        private static List<ProjectInfo> ParseProjectInfo(ConfigFile config)
        {
            List<ProjectInfo> projectInfoList = new List<ProjectInfo>();
            string solutionDir = Path.GetDirectoryName(config.SolutionPath);

            // Read all currently existing nuspec files to match them with project files
            Dictionary<string,XDocument> nuspecFiles = new Dictionary<string,XDocument>();
            foreach (string nuspecFile in Directory.EnumerateFiles(config.NuSpecRootDir, "*.nuspec", SearchOption.TopDirectoryOnly))
            {
                XDocument doc = XDocument.Load(nuspecFile);
                nuspecFiles.Add(nuspecFile, doc);
            }

            // Scan the solutions directory tree for projects
            foreach (string projectFile in Directory.EnumerateFiles(solutionDir, "*.csproj", SearchOption.AllDirectories))
            {
                ProjectInfo info = new ProjectInfo
                {
                    ProjectFilePath = projectFile,
                    ProjectRootDir = Path.GetDirectoryName(projectFile)
                };

                // Determine the project's AssemblyInfo file
                info.AssemblyInfoFilePath = Directory.EnumerateFiles(info.ProjectRootDir, "AssemblyInfo.cs", SearchOption.AllDirectories)
                    .OrderBy(path => GetPathDepth(path))
                    .FirstOrDefault();

                // Determine which nuspec belongs to this project
                foreach (var pair in nuspecFiles)
                {
                    string nuspecFilePath = pair.Key;
                    string nuspecFileDir = Path.GetDirectoryName(nuspecFilePath);
                    XDocument nuspecDoc = pair.Value;

                    // Does it reference any file from this projects directory structure?
                    bool anyFileInProjectRoot = false;
                    foreach (XElement fileElement in nuspecDoc.Descendants("file"))
                    {
                        XAttribute srcAttrib = fileElement.Attribute("src");
                        if (srcAttrib == null) continue;

                        string relativePath = srcAttrib.Value;
                        string relativePathWithoutWildcards = relativePath.Split('*')[0];
                        string filePathBase = Path.Combine(nuspecFileDir, relativePathWithoutWildcards);
                        if (IsPathLocatedIn(filePathBase, info.ProjectRootDir))
                        {
                            anyFileInProjectRoot = true;
                            break;
                        }
                    }
                    if (!anyFileInProjectRoot) continue;

                    // If it does, it probably belongs to this project. Retrieve some data and stop searching.
                    XElement elemId = nuspecDoc.Descendants("id").FirstOrDefault();
                    XElement elemVersion = nuspecDoc.Descendants("version").FirstOrDefault();
                    info.NuSpecFilePath = nuspecFilePath;
                    info.NuSpecPackageId = elemId.Value.Trim();
                    info.NuSpecVersion = Version.Parse(elemVersion.Value.Trim());
                    break;
                }

                // If we failed to associate the project with a NuSpec, discard it
                if (info.NuSpecPackageId == null) continue;
                if (info.NuSpecVersion == null) continue;

                // Otherwise, keep it for later
                projectInfoList.Add(info);
            }

            projectInfoList.Sort((a, b) => string.Compare(a.NuSpecPackageId, b.NuSpecPackageId));
            return projectInfoList;
        }
Exemple #5
0
        private static void RetrieveUpdateModes(ConfigFile config, IEnumerable<ProjectChangeInfo> changes)
        {
            string solutionDir = Path.GetDirectoryName(config.SolutionPath);

            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("Collecting Update data...");
            Console.ResetColor();
            Console.WriteLine();
            foreach (ProjectChangeInfo changeInfo in changes)
            {
                string[] nameTokens = changeInfo.Project.NuSpecPackageId.Split('.');

                Console.WriteLine();
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("{0} {1}", changeInfo.Project.NuSpecPackageId, changeInfo.Project.NuSpecVersion);
                Console.ResetColor();
                Console.WriteLine();

                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine("  {0} files changed:", changeInfo.ModifiedFilePaths.Count);
                Console.ResetColor();
                foreach (string changedFile in changeInfo.ModifiedFilePaths)
                {
                    string displayedPath = GetRelativePath(changedFile, solutionDir);
                    string displayedDir = Path.GetDirectoryName(displayedPath);
                    string displayedFileNameWithoutExt = Path.GetFileNameWithoutExtension(displayedPath);
                    string displayedExt = Path.GetExtension(displayedPath);

                    Console.ForegroundColor = ConsoleColor.DarkGray;
                    Console.Write("    {0}", displayedDir);
                    Console.Write(Path.DirectorySeparatorChar);
                    Console.ResetColor();
                    Console.Write(displayedFileNameWithoutExt);
                    Console.ForegroundColor = ConsoleColor.DarkGreen;
                    Console.WriteLine(displayedExt);
                    Console.ResetColor();
                }
                Console.WriteLine();

                Console.WriteLine("  {0}", string.Join(", ", changeInfo.Titles));
                foreach (string changeLine in changeInfo.ChangeLog)
                {
                    Console.ForegroundColor = ConsoleColor.DarkGray;
                    Console.WriteLine("    {0}", changeLine);
                    Console.ResetColor();
                }
                Console.WriteLine();

                Console.Write("  Update? ");
                Console.ForegroundColor = ConsoleColor.DarkGray;
                Console.Write("(m)ajor, m(i)nor, (p)atch, (n)one / (s)kip");
                Console.WriteLine();
                Console.ResetColor();
                Console.Write("  ");
                string userInput = Console.ReadLine();

                // Parse user input
                switch (userInput.ToLower())
                {
                    case "major":
                    case "m":
                        changeInfo.UpdateMode = UpdateMode.Major;
                        break;
                    case "minor":
                    case "i":
                        changeInfo.UpdateMode = UpdateMode.Minor;
                        break;
                    case "patch":
                    case "p":
                        changeInfo.UpdateMode = UpdateMode.Patch;
                        break;
                    default:
                    case "none":
                    case "skip":
                    case "n":
                    case "s":
                        changeInfo.UpdateMode = UpdateMode.None;
                        break;
                }
                Console.ForegroundColor = ConsoleColor.DarkGray;
                Console.WriteLine("  Updating {0} Version", changeInfo.UpdateMode);
                Console.ResetColor();
                Console.WriteLine();
            }
        }
Exemple #6
0
        private static HashSet<PropertyInfo> ParseCommandLine(ConfigFile config, string[] args)
        {
            PropertyInfo[] configProps = typeof(ConfigFile).GetProperties();
            HashSet<PropertyInfo> configOverride = new HashSet<PropertyInfo>();

            foreach (string arg in args)
            {
                string[] token = arg.Split('=');
                if (token.Length != 2) continue;

                PropertyInfo prop = configProps.FirstOrDefault(p => p.Name.ToLower() == token[0].Trim().ToLower());
                if (prop == null) continue;

                object value = null;
                try
                {
                    value = Convert.ChangeType(token[1].Trim(), prop.PropertyType, System.Globalization.CultureInfo.InvariantCulture);
                }
                catch {}
                if (value == null) continue;

                prop.SetValue(config, value, null);
                configOverride.Add(prop);
            }

            return configOverride;
        }
Exemple #7
0
        private static List<GitCommitInfo> ParseGitCommitsSinceLastPackage(ConfigFile config, string gitPath)
        {
            List<GitCommitInfo> commitsSinceLastPackage = new List<GitCommitInfo>();
            string solutionDir = Path.GetDirectoryName(config.SolutionPath);

            // Retrieve the git history for the repository
            ProcessStartInfo gitStartInfo = new ProcessStartInfo
            {
                FileName = gitPath,
                WorkingDirectory = solutionDir,
                Arguments = "--no-pager log --name-only --oneline --since=\"2015\"",
                UseShellExecute = false,
                RedirectStandardOutput = true,
                CreateNoWindow = true,
                WindowStyle = ProcessWindowStyle.Hidden
            };
            Process gitProc = new Process();
            gitProc.StartInfo = gitStartInfo;
            gitProc.OutputDataReceived += delegate (object sender, DataReceivedEventArgs e)
            {
                if (string.IsNullOrEmpty(e.Data)) return;

                // Begin of a new commit
                if (e.Data.Contains(' '))
                {
                    string line = e.Data.Trim();
                    int indexOfSeparator = line.IndexOf(' ');
                    string commitId = line.Substring(0, indexOfSeparator);

                    if (Regex.IsMatch(commitId, @"\b[0-9a-f]{5,40}\b"))
                    {
                        string commitTitleAndMessage = line.Substring(indexOfSeparator, line.Length - indexOfSeparator).Trim();
                        string[] commitMessageToken = commitTitleAndMessage.Split('#');
                        string commitTitle;
                        string commitMessage;

                        // Received the expected commit message format in the form
                        //
                        // Title / Headline
                        // #CHANGE: Description
                        // #ADD: More Description
                        // ...
                        if (commitMessageToken.Length > 1)
                        {
                            commitTitle = commitMessageToken[0].Trim();
                            commitMessage = "#" + string.Join(Environment.NewLine + "#", commitMessageToken, 1, commitMessageToken.Length - 1);
                        }
                        // Received an unexpected message format
                        else
                        {
                            commitMessageToken = commitTitleAndMessage.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                            commitTitle = commitMessageToken[0];
                            commitMessage = string.Empty;
                        }

                        // Stop reading as soon as we see a package update commit
                        if (string.Equals(commitTitle, PackageUpdateCommitTitle, StringComparison.InvariantCultureIgnoreCase) &&
                            commitMessage.IndexOf(PackageUpdateCommitMessageBegin, StringComparison.InvariantCultureIgnoreCase) >= 0)
                        {
                            gitProc.CancelOutputRead();
                            if (!gitProc.HasExited)
                                gitProc.Kill();
                            return;
                        }

                        // Start collecting files for the new commit
                        commitsSinceLastPackage.Add(new GitCommitInfo
                        {
                            Id = commitId,
                            Title = commitTitle,
                            Message = commitMessage
                        });
                        return;
                    }
                }

                // File list entry
                GitCommitInfo commitInfo = commitsSinceLastPackage[commitsSinceLastPackage.Count - 1];
                string filePathFull = Path.GetFullPath(Path.Combine(solutionDir, e.Data));
                commitInfo.FilePaths.Add(filePathFull);
                return;
            };
            gitProc.Start();
            gitProc.BeginOutputReadLine();
            gitProc.WaitForExit();

            return commitsSinceLastPackage;
        }
Exemple #8
0
        private static List<ProjectChangeInfo> GetChangesPerProject(ConfigFile config, IEnumerable<ProjectInfo> allProjects, IEnumerable<GitCommitInfo> gitHistory)
        {
            Dictionary<ProjectInfo, ProjectChangeInfo> changesPerProject = new Dictionary<ProjectInfo,ProjectChangeInfo>();
            foreach (GitCommitInfo commit in gitHistory)
            {
                // Iterate over all projects that are affected by this commit
                foreach (ProjectInfo project in allProjects)
                {
                    bool isAffectedByCommit = false;

                    // Determine projects that are located inside this project's root directory
                    ProjectInfo[] subProjects = allProjects
                        .Where(p => p != project && IsPathLocatedIn(p.ProjectRootDir, project.ProjectRootDir))
                        .ToArray();

                    // Are files of this project modified?
                    List<string> localModifiedFiles = new List<string>();
                    foreach (string filePath in commit.FilePaths)
                    {
                        // Not located in this project? Skip.
                        if (!IsPathLocatedIn(filePath, project.ProjectRootDir)) continue;
                        // Located in a sub-project? Skip.
                        if (subProjects.Any(p => IsPathLocatedIn(filePath, p.ProjectRootDir))) continue;

                        isAffectedByCommit = true;
                        localModifiedFiles.Add(filePath);
                    }

                    // Skip all projects that are not affected by the commit at all
                    if (!isAffectedByCommit) continue;

                    // Retrieve the change log information about this project
                    ProjectChangeInfo changeInfo;
                    if (!changesPerProject.TryGetValue(project, out changeInfo))
                    {
                        changeInfo = new ProjectChangeInfo
                        {
                            Project = project,
                        };
                        changesPerProject.Add(project, changeInfo);
                    }

                    // Add modified files
                    foreach (string filePath in localModifiedFiles)
                    {
                        changeInfo.ModifiedFilePaths.Add(filePath);
                    }

                    // Add changelog entries, if we have meaningful comments
                    if (!string.IsNullOrEmpty(commit.Title) && !string.IsNullOrEmpty(commit.Message))
                    {
                        string[] msgItems = commit.Message.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
                        changeInfo.Titles.Add(commit.Title);
                        changeInfo.ChangeLog.AddRange(msgItems);
                    }
                }
            }

            // For every non-default project, check if there was a non-default-project dependency
            // modified and add a version bump change. This will make sure sample projects
            // will always depend on the latest released version of their core and editor projects.
            foreach (ProjectInfo dependency in changesPerProject.Keys.ToArray())
            {
                if (dependency.IsDefaultPackage) continue;

                foreach (ProjectInfo dependent in allProjects)
                {
                    if (dependent.IsDefaultPackage)
                        continue;
                    if (!dependent.NuSpecDependencies.Contains(dependency.NuSpecPackageId))
                        continue;
                    if (changesPerProject.ContainsKey(dependent))
                        continue;

                    // Create a new change info for the dependent project
                    ProjectChangeInfo changeInfo;
                    changeInfo = new ProjectChangeInfo
                    {
                        Project = dependent
                    };
                    changesPerProject.Add(dependent, changeInfo);

                    // Add a dummy change log about this project
                    changeInfo.Titles.Add("Updated Dependencies");
                    changeInfo.ChangeLog.Add(string.Format("#CHANGE: Updated {0} dependency", dependency.NuSpecPackageId));
                }
            }

            return changesPerProject.Values.ToList();
        }
Exemple #9
0
        private static List<ProjectChangeInfo> GetChangesPerProject(ConfigFile config, IEnumerable<ProjectInfo> allProjects, IEnumerable<GitCommitInfo> gitHistory)
        {
            Dictionary<ProjectInfo, ProjectChangeInfo> changesPerProject = new Dictionary<ProjectInfo,ProjectChangeInfo>();
            foreach (GitCommitInfo commit in gitHistory)
            {
                // Iterate over all projects that are affected by this commit
                foreach (ProjectInfo project in allProjects)
                {
                    bool isAffectedByCommit = false;
                    List<string> localModifiedFiles = new List<string>();
                    foreach (string filePath in commit.FilePaths)
                    {
                        if (IsPathLocatedIn(filePath, project.ProjectRootDir))
                        {
                            isAffectedByCommit = true;
                            localModifiedFiles.Add(filePath);
                        }
                    }
                    if (!isAffectedByCommit) continue;

                    // Retrieve the change log information about this project
                    ProjectChangeInfo changeInfo;
                    if (!changesPerProject.TryGetValue(project, out changeInfo))
                    {
                        changeInfo = new ProjectChangeInfo
                        {
                            Project = project,
                        };
                        changesPerProject.Add(project, changeInfo);
                    }

                    // Add modified files
                    foreach (string filePath in localModifiedFiles)
                    {
                        changeInfo.ModifiedFilePaths.Add(filePath);
                    }

                    // Add changelog entries, if we have meaningful comments
                    if (!string.IsNullOrEmpty(commit.Title) && !string.IsNullOrEmpty(commit.Message))
                    {
                        string[] msgItems = commit.Message.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
                        changeInfo.Titles.Add(commit.Title);
                        changeInfo.ChangeLog.AddRange(msgItems);
                    }
                }
            }
            return changesPerProject.Values.ToList();
        }
Exemple #10
0
        private static void RetrieveUpdateModes(ConfigFile config, IEnumerable <ProjectChangeInfo> changes)
        {
            string solutionDir = Path.GetDirectoryName(config.SolutionPath);

            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("Collecting Update data...");
            Console.ResetColor();
            Console.WriteLine();
            foreach (ProjectChangeInfo changeInfo in changes)
            {
                string[] nameTokens = changeInfo.Project.NuSpecPackageId.Split('.');

                Console.WriteLine();
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("{0} {1}", changeInfo.Project.NuSpecPackageId, changeInfo.Project.NuSpecVersion);
                Console.ResetColor();
                Console.WriteLine();

                Console.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine("  {0} files changed:", changeInfo.ModifiedFilePaths.Count);
                Console.ResetColor();
                foreach (string changedFile in changeInfo.ModifiedFilePaths)
                {
                    string displayedPath = GetRelativePath(changedFile, solutionDir);
                    string displayedDir  = Path.GetDirectoryName(displayedPath);
                    string displayedFileNameWithoutExt = Path.GetFileNameWithoutExtension(displayedPath);
                    string displayedExt = Path.GetExtension(displayedPath);

                    Console.ForegroundColor = ConsoleColor.DarkGray;
                    Console.Write("    {0}", displayedDir);
                    Console.Write(Path.DirectorySeparatorChar);
                    Console.ResetColor();
                    Console.Write(displayedFileNameWithoutExt);
                    Console.ForegroundColor = ConsoleColor.DarkGreen;
                    Console.WriteLine(displayedExt);
                    Console.ResetColor();
                }
                Console.WriteLine();

                Console.WriteLine("  {0}", string.Join(", ", changeInfo.Titles));
                foreach (string changeLine in changeInfo.ChangeLog)
                {
                    Console.ForegroundColor = ConsoleColor.DarkGray;
                    Console.WriteLine("    {0}", changeLine);
                    Console.ResetColor();
                }
                Console.WriteLine();

                Console.Write("  Update? ");
                Console.ForegroundColor = ConsoleColor.DarkGray;
                Console.Write("(m)ajor, m(i)nor, (p)atch, (n)one / (s)kip");
                Console.WriteLine();
                Console.ResetColor();
                Console.Write("  ");
                string userInput = Console.ReadLine();

                // Parse user input
                switch (userInput.ToLower())
                {
                case "major":
                case "m":
                    changeInfo.UpdateMode = UpdateMode.Major;
                    break;

                case "minor":
                case "i":
                    changeInfo.UpdateMode = UpdateMode.Minor;
                    break;

                case "patch":
                case "p":
                    changeInfo.UpdateMode = UpdateMode.Patch;
                    break;

                default:
                case "none":
                case "skip":
                case "n":
                case "s":
                    changeInfo.UpdateMode = UpdateMode.None;
                    break;
                }
                Console.ForegroundColor = ConsoleColor.DarkGray;
                Console.WriteLine("  Updating {0} Version", changeInfo.UpdateMode);
                Console.ResetColor();
                Console.WriteLine();
            }
        }
Exemple #11
0
        public static void Main(string[] args)
        {
            string     configFileName = "VersionUpdaterConfig.xml";
            string     configFileDir  = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath);
            string     configFilePath = Path.Combine(configFileDir, configFileName);
            ConfigFile config         = ConfigFile.Load(configFilePath);

            HashSet <PropertyInfo> configOverride;
            string                   gitPath;
            List <ProjectInfo>       allProjects;
            List <GitCommitInfo>     gitHistory;
            List <ProjectChangeInfo> changes;

            // Parse command line arguments and use them to override config properties
            configOverride = ParseCommandLine(config, args);
            string solutionDir = Path.GetDirectoryName(config.SolutionPath);

            // Write some diagnostic data to the log
            {
                Console.WriteLine("VersionUpdater launched");
                Console.WriteLine("Working Dir: {0}", Environment.CurrentDirectory);
                Console.WriteLine("Relative Base Dir: {0}", configFileDir);
                Console.WriteLine("Command Line: {0}", args.Aggregate("", (acc, arg) => acc + " " + arg));
                Console.WriteLine("Config:");
                foreach (PropertyInfo prop in typeof(ConfigFile).GetProperties())
                {
                    if (configOverride.Contains(prop))
                    {
                        Console.ForegroundColor = ConsoleColor.White;
                    }
                    Console.WriteLine("  {0}: {1}", prop.Name, prop.GetValue(config, null));
                    Console.ForegroundColor = ConsoleColor.Gray;
                }
                Console.WriteLine();
                Console.WriteLine();
            }

            // Update all config file paths to be absolute and account for the fact that
            // the working directory might be different from the one where executable and
            // config file are located
            UpdateConfigPaths(config, configFileDir);

            // Determine the path of a usable git executable
            gitPath = SearchGitPath(config);

            Console.WriteLine("Git executable path: '{0}'", gitPath);
            Console.WriteLine();

            // Check if there are any local changes in the git repo
            {
                bool             anyModifiedFiles = false;
                ProcessStartInfo gitStartInfo     = new ProcessStartInfo
                {
                    FileName               = gitPath,
                    WorkingDirectory       = solutionDir,
                    Arguments              = "--no-pager ls-files -m",
                    UseShellExecute        = false,
                    RedirectStandardOutput = true,
                    CreateNoWindow         = true,
                    WindowStyle            = ProcessWindowStyle.Hidden
                };
                Process gitProc = new Process();
                gitProc.StartInfo           = gitStartInfo;
                gitProc.OutputDataReceived += delegate(object sender, DataReceivedEventArgs e)
                {
                    // If we receive any non-empty lines, those are modified files
                    if (!string.IsNullOrEmpty(e.Data))
                    {
                        anyModifiedFiles = true;
                        gitProc.CancelOutputRead();
                        if (!gitProc.HasExited)
                        {
                            gitProc.Kill();
                        }
                        return;
                    }
                };
                gitProc.Start();
                gitProc.BeginOutputReadLine();
                gitProc.WaitForExit();

                if (anyModifiedFiles)
                {
                    Console.WriteLine();
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine("  There are modified files in your local git repository. Please make sure to start the version updater tool only with a clean working copy and no staged files.");
                    Console.ResetColor();
                    Console.WriteLine();
                    return;
                }
            }

            // Retrieve information about all project files and their nuspec associations
            allProjects = ParseProjectInfo(config);

            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("Found {0} projects:", allProjects.Count);
            Console.ResetColor();
            Console.WriteLine();
            int maxProjectNameLen = allProjects.Max(p => p.NuSpecPackageId.Length - p.NuSpecPackageId.LastIndexOf('.') - 1);

            foreach (ProjectInfo project in allProjects)
            {
                string[] nameTokens           = project.NuSpecPackageId.Split('.');
                string   displayedProjectName = nameTokens.LastOrDefault();

                Console.Write("  {0}", displayedProjectName.PadRight(maxProjectNameLen, ' '));
                Console.Write("{0,7}", project.NuSpecVersion);
                Console.ForegroundColor = ConsoleColor.DarkGray;
                Console.WriteLine(" in '{0}'", GetRelativePath(project.ProjectRootDir, solutionDir));
                Console.ResetColor();
            }
            Console.WriteLine();

            // Retrieve information about the git history since we last did a package update
            gitHistory = ParseGitCommitsSinceLastPackage(config, gitPath);

            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("Logged {0} commits since last package update:", gitHistory.Count);
            Console.ResetColor();
            Console.WriteLine();
            foreach (GitCommitInfo commit in gitHistory)
            {
                Console.ForegroundColor = ConsoleColor.DarkGray;
                Console.Write("  {0}: ", commit.Id);
                Console.ResetColor();
                Console.Write("'{0}', ", commit.Title);
                Console.ForegroundColor = ConsoleColor.Blue;
                Console.WriteLine("{0} files changed", commit.FilePaths.Count);
                Console.ResetColor();
            }
            Console.WriteLine();

            // Determine the changes for each project individually and gather changelog entries
            changes = GetChangesPerProject(config, allProjects, gitHistory);

            // Display each change to the user and ask whether it should be considered a patch, minor or major release
            RetrieveUpdateModes(config, changes);

            // Apply the specified update modes to the version numbers of each project
            UpdateVersionNumbers(config, changes);

            // Remove change entries without an update
            changes.RemoveAll(c => c.UpdateMode == UpdateMode.None);

            // Apply version numbers and change logs to nuspecs and projects
            foreach (ProjectChangeInfo changeInfo in changes)
            {
                string versionString = string.Format("{0}.{1}.{2}",
                                                     changeInfo.Project.NuSpecVersion.Major,
                                                     changeInfo.Project.NuSpecVersion.Minor,
                                                     changeInfo.Project.NuSpecVersion.Build);

                // Update AssemblyInfo version
                {
                    string[] assemblyInfoLines = File.ReadAllLines(changeInfo.Project.AssemblyInfoFilePath);
                    for (int i = 0; i < assemblyInfoLines.Length; i++)
                    {
                        string line = assemblyInfoLines[i];
                        if (!line.Contains("AssemblyVersion") && !line.Contains("AssemblyFileVersion"))
                        {
                            continue;
                        }

                        int beginIndex = line.IndexOf('(');
                        int endIndex   = line.IndexOf(')');
                        if (beginIndex == -1)
                        {
                            continue;
                        }
                        if (endIndex == -1)
                        {
                            continue;
                        }

                        line =
                            line.Substring(0, beginIndex) +
                            "(\"" + versionString + "\")" +
                            line.Substring(endIndex + 1, line.Length - endIndex - 1);
                        assemblyInfoLines[i] = line;
                    }
                    File.WriteAllLines(changeInfo.Project.AssemblyInfoFilePath, assemblyInfoLines);
                }

                // Update nuspec version, release notes and dependencies
                {
                    XDocument nuspecDoc           = XDocument.Load(changeInfo.Project.NuSpecFilePath);
                    XElement  metadataElement     = nuspecDoc.Descendants("metadata").FirstOrDefault();
                    XElement  versionElement      = metadataElement.Descendants("version").FirstOrDefault();
                    XElement  releaseNotesElement = metadataElement.Descendants("releaseNotes").FirstOrDefault();
                    XElement  dependenciesElement = metadataElement.Descendants("dependencies").FirstOrDefault();
                    if (releaseNotesElement == null)
                    {
                        releaseNotesElement = new XElement("releaseNotes");
                        metadataElement.Add(releaseNotesElement);
                    }

                    versionElement.Value      = versionString;
                    releaseNotesElement.Value =
                        string.Join(", ", changeInfo.Titles.Take(3)) +
                        Environment.NewLine +
                        string.Join(Environment.NewLine, changeInfo.ChangeLog);
                    if (dependenciesElement != null)
                    {
                        foreach (XElement dependencyElement in dependenciesElement.Descendants("dependency"))
                        {
                            XAttribute idAttrib      = dependencyElement.Attribute("id");
                            XAttribute versionAttrib = dependencyElement.Attribute("version");
                            if (idAttrib == null)
                            {
                                continue;
                            }
                            if (versionAttrib == null)
                            {
                                continue;
                            }

                            // Since multiple projects might be part of the same nuspec and either of them might
                            // have bumped the package version, we'll have to use the highest version number in
                            // our dependency reference. A proper solution would be to separate project from
                            // package info, but this will do for now.
                            Version highestDependencyVersion = allProjects
                                                               .Where(p => string.Equals(p.NuSpecPackageId, idAttrib.Value, StringComparison.InvariantCultureIgnoreCase))
                                                               .Max(p => p.NuSpecVersion);
                            if (highestDependencyVersion == null)
                            {
                                continue;
                            }

                            versionAttrib.Value = string.Format("{0}.{1}.{2}",
                                                                highestDependencyVersion.Major,
                                                                highestDependencyVersion.Minor,
                                                                highestDependencyVersion.Build);
                        }
                    }

                    nuspecDoc.Save(changeInfo.Project.NuSpecFilePath);
                }
            }

            // Perform a git commit with an auto-generated message
            if (changes.Count > 0)
            {
                Console.WriteLine();
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("Performing Git Commit...");
                Console.ResetColor();
                Console.WriteLine();

                string commitMsgFileName = "PackageUpdateCommitMsg.txt";
                string commitMsgFilePath = Path.GetFullPath(Path.Combine(solutionDir, commitMsgFileName));

                // Build the commit message
                StringBuilder messageBuilder = new StringBuilder();
                messageBuilder.AppendLine(PackageUpdateCommitTitle);
                foreach (ProjectChangeInfo changeInfo in changes)
                {
                    string versionString = string.Format("{0}.{1}.{2}",
                                                         changeInfo.Project.NuSpecVersion.Major,
                                                         changeInfo.Project.NuSpecVersion.Minor,
                                                         changeInfo.Project.NuSpecVersion.Build);
                    messageBuilder.AppendFormat(PackageUpdateCommitMessage, changeInfo.Project.NuSpecPackageId, versionString);
                    messageBuilder.AppendLine();
                }
                File.WriteAllText(commitMsgFilePath, messageBuilder.ToString());

                // Stage all files in git
                {
                    ProcessStartInfo gitStartInfo = new ProcessStartInfo
                    {
                        FileName         = gitPath,
                        WorkingDirectory = solutionDir,
                        Arguments        = "add -u",
                        UseShellExecute  = false,
                    };
                    Process gitProc = Process.Start(gitStartInfo);
                    gitProc.WaitForExit();
                }

                // Execute a git commit
                {
                    ProcessStartInfo gitStartInfo = new ProcessStartInfo
                    {
                        FileName         = gitPath,
                        WorkingDirectory = solutionDir,
                        Arguments        = "commit -F " + commitMsgFileName,
                        UseShellExecute  = false,
                    };
                    Process gitProc = Process.Start(gitStartInfo);
                    gitProc.WaitForExit();
                }

                // Remove our temporary commit message file
                File.Delete(commitMsgFilePath);
            }

            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine("All done!");
            Console.WriteLine();
        }
Exemple #12
0
        private static List <ProjectChangeInfo> GetChangesPerProject(ConfigFile config, IEnumerable <ProjectInfo> allProjects, IEnumerable <GitCommitInfo> gitHistory)
        {
            Dictionary <ProjectInfo, ProjectChangeInfo> changesPerProject = new Dictionary <ProjectInfo, ProjectChangeInfo>();

            foreach (GitCommitInfo commit in gitHistory)
            {
                // Iterate over all projects that are affected by this commit
                foreach (ProjectInfo project in allProjects)
                {
                    bool isAffectedByCommit = false;

                    // Determine projects that are located inside this project's root directory
                    ProjectInfo[] subProjects = allProjects
                                                .Where(p => p != project && IsPathLocatedIn(p.ProjectRootDir, project.ProjectRootDir))
                                                .ToArray();

                    // Are files of this project modified?
                    List <string> localModifiedFiles = new List <string>();
                    foreach (string filePath in commit.FilePaths)
                    {
                        // Not located in this project? Skip.
                        if (!IsPathLocatedIn(filePath, project.ProjectRootDir))
                        {
                            continue;
                        }
                        // Located in a sub-project? Skip.
                        if (subProjects.Any(p => IsPathLocatedIn(filePath, p.ProjectRootDir)))
                        {
                            continue;
                        }

                        isAffectedByCommit = true;
                        localModifiedFiles.Add(filePath);
                    }

                    // Skip all projects that are not affected by the commit at all
                    if (!isAffectedByCommit)
                    {
                        continue;
                    }

                    // Retrieve the change log information about this project
                    ProjectChangeInfo changeInfo;
                    if (!changesPerProject.TryGetValue(project, out changeInfo))
                    {
                        changeInfo = new ProjectChangeInfo
                        {
                            Project = project,
                        };
                        changesPerProject.Add(project, changeInfo);
                    }

                    // Add modified files
                    foreach (string filePath in localModifiedFiles)
                    {
                        changeInfo.ModifiedFilePaths.Add(filePath);
                    }

                    // Add changelog entries, if we have meaningful comments
                    if (!string.IsNullOrEmpty(commit.Title) && !string.IsNullOrEmpty(commit.Message))
                    {
                        string[] msgItems = commit.Message.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
                        changeInfo.Titles.Add(commit.Title);
                        changeInfo.ChangeLog.AddRange(msgItems);
                    }
                }
            }

            // For every non-default project, check if there was a non-default-project dependency
            // modified and add a version bump change. This will make sure sample projects
            // will always depend on the latest released version of their core and editor projects.
            foreach (ProjectInfo dependency in changesPerProject.Keys.ToArray())
            {
                if (dependency.IsDefaultPackage)
                {
                    continue;
                }

                foreach (ProjectInfo dependent in allProjects)
                {
                    if (dependent.IsDefaultPackage)
                    {
                        continue;
                    }
                    if (!dependent.NuSpecDependencies.Contains(dependency.NuSpecPackageId))
                    {
                        continue;
                    }
                    if (changesPerProject.ContainsKey(dependent))
                    {
                        continue;
                    }

                    // Create a new change info for the dependent project
                    ProjectChangeInfo changeInfo;
                    changeInfo = new ProjectChangeInfo
                    {
                        Project = dependent
                    };
                    changesPerProject.Add(dependent, changeInfo);

                    // Add a dummy change log about this project
                    changeInfo.Titles.Add("Updated Dependencies");
                    changeInfo.ChangeLog.Add(string.Format("#CHANGE: Updated {0} dependency", dependency.NuSpecPackageId));
                }
            }

            return(changesPerProject.Values.ToList());
        }
Exemple #13
0
        private static List <GitCommitInfo> ParseGitCommitsSinceLastPackage(ConfigFile config, string gitPath)
        {
            List <GitCommitInfo> commitsSinceLastPackage = new List <GitCommitInfo>();
            string solutionDir = Path.GetDirectoryName(config.SolutionPath);

            // Retrieve the git history for the repository
            ProcessStartInfo gitStartInfo = new ProcessStartInfo
            {
                FileName               = gitPath,
                WorkingDirectory       = solutionDir,
                Arguments              = "--no-pager log --name-only --oneline --since=\"2015\"",
                UseShellExecute        = false,
                RedirectStandardOutput = true,
                CreateNoWindow         = true,
                WindowStyle            = ProcessWindowStyle.Hidden
            };
            Process gitProc = new Process();

            gitProc.StartInfo           = gitStartInfo;
            gitProc.OutputDataReceived += delegate(object sender, DataReceivedEventArgs e)
            {
                if (string.IsNullOrEmpty(e.Data))
                {
                    return;
                }

                // Begin of a new commit
                if (e.Data.Contains(' '))
                {
                    string line             = e.Data.Trim();
                    int    indexOfSeparator = line.IndexOf(' ');
                    string commitId         = line.Substring(0, indexOfSeparator);

                    if (Regex.IsMatch(commitId, @"\b[0-9a-f]{5,40}\b"))
                    {
                        string   commitTitleAndMessage = line.Substring(indexOfSeparator, line.Length - indexOfSeparator).Trim();
                        string[] commitMessageToken    = commitTitleAndMessage.Split('#');
                        string   commitTitle;
                        string   commitMessage;

                        // Received the expected commit message format in the form
                        //
                        // Title / Headline
                        // #CHANGE: Description
                        // #ADD: More Description
                        // ...
                        if (commitMessageToken.Length > 1)
                        {
                            commitTitle = commitMessageToken[0].Trim();

                            // Filter out invalid hashtags by re-joining their token
                            List <string> filteredMessageToken = new List <string>();
                            for (int i = 1; i < commitMessageToken.Length; i++)
                            {
                                string token = commitMessageToken[i];
                                bool   startsWithValidHashtag = Regex.IsMatch(token, @"\b[A-Z]+\:\s");
                                if (startsWithValidHashtag)
                                {
                                    filteredMessageToken.Add(token);
                                }
                                else if (filteredMessageToken.Count > 0)
                                {
                                    string lastToken = filteredMessageToken[filteredMessageToken.Count - 1];
                                    filteredMessageToken.RemoveAt(filteredMessageToken.Count - 1);
                                    filteredMessageToken.Add(lastToken + "#" + token);
                                }
                            }
                            commitMessage = "#" + string.Join(Environment.NewLine + "#", filteredMessageToken);
                        }
                        // Received an unexpected message format
                        else
                        {
                            commitMessageToken = commitTitleAndMessage.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                            commitTitle        = commitMessageToken[0];
                            commitMessage      = string.Empty;
                        }

                        // Stop reading as soon as we see a package update commit
                        if (string.Equals(commitTitle, PackageUpdateCommitTitle, StringComparison.InvariantCultureIgnoreCase) &&
                            commitMessage.IndexOf(PackageUpdateCommitMessageBegin, StringComparison.InvariantCultureIgnoreCase) >= 0)
                        {
                            gitProc.CancelOutputRead();
                            if (!gitProc.HasExited)
                            {
                                gitProc.Kill();
                            }
                            return;
                        }

                        // Start collecting files for the new commit
                        commitsSinceLastPackage.Add(new GitCommitInfo
                        {
                            Id      = commitId,
                            Title   = commitTitle,
                            Message = commitMessage
                        });
                        return;
                    }
                }

                // File list entry
                GitCommitInfo commitInfo   = commitsSinceLastPackage[commitsSinceLastPackage.Count - 1];
                string        filePathFull = Path.GetFullPath(Path.Combine(solutionDir, e.Data));
                commitInfo.FilePaths.Add(filePathFull);
                return;
            };
            gitProc.Start();
            gitProc.BeginOutputReadLine();
            gitProc.WaitForExit();

            return(commitsSinceLastPackage);
        }
Exemple #14
0
        private static List <ProjectInfo> ParseProjectInfo(ConfigFile config)
        {
            List <ProjectInfo> projectInfoList = new List <ProjectInfo>();
            string             solutionDir     = Path.GetDirectoryName(config.SolutionPath);

            // Read all currently existing nuspec files to match them with project files
            Dictionary <string, XDocument> nuspecFiles = new Dictionary <string, XDocument>();

            foreach (string nuspecFile in Directory.EnumerateFiles(config.NuSpecRootDir, "*.nuspec", SearchOption.TopDirectoryOnly))
            {
                XDocument doc = XDocument.Load(nuspecFile);
                nuspecFiles.Add(nuspecFile, doc);
            }

            // Scan the solutions directory tree for projects
            foreach (string projectFile in Directory.EnumerateFiles(solutionDir, "*.csproj", SearchOption.AllDirectories))
            {
                ProjectInfo info = new ProjectInfo
                {
                    ProjectFilePath = projectFile,
                    ProjectRootDir  = Path.GetDirectoryName(projectFile),
                    OutputFilePaths = new List <string>()
                };

                // Parse the project file to determine output paths
                {
                    XDocument        projectDoc   = XDocument.Load(projectFile);
                    string           projectDocNs = projectDoc.Root.GetDefaultNamespace().NamespaceName;
                    HashSet <string> outputDirs   = new HashSet <string>();
                    HashSet <string> outputNames  = new HashSet <string>();
                    foreach (XElement outPathElement in projectDoc.Descendants(XName.Get("OutputPath", projectDocNs)))
                    {
                        string path = outPathElement.Value;
                        path = path.Replace("$(SolutionDir)", solutionDir + Path.DirectorySeparatorChar);
                        if (!Path.IsPathRooted(path))
                        {
                            path = Path.Combine(info.ProjectRootDir, path);
                        }
                        outputDirs.Add(path);
                    }
                    foreach (XElement outPathElement in projectDoc.Descendants(XName.Get("AssemblyName", projectDocNs)))
                    {
                        string name = outPathElement.Value;
                        outputNames.Add(name);
                    }
                    string[] extensions = new string[] { ".exe", ".dll" };
                    foreach (string dir in outputDirs)
                    {
                        foreach (string name in outputNames)
                        {
                            foreach (string ext in extensions)
                            {
                                string path = Path.Combine(dir, name) + ext;
                                if (File.Exists(path))
                                {
                                    info.OutputFilePaths.Add(path);
                                }
                            }
                        }
                    }
                }

                // Determine the project's AssemblyInfo file
                info.AssemblyInfoFilePath = Directory.EnumerateFiles(info.ProjectRootDir, "AssemblyInfo.cs", SearchOption.AllDirectories)
                                            .OrderBy(path => GetPathDepth(path))
                                            .FirstOrDefault();

                // Determine which nuspec belongs to this project
                foreach (var pair in nuspecFiles)
                {
                    string    nuspecFilePath = pair.Key;
                    string    nuspecFileDir  = Path.GetDirectoryName(nuspecFilePath);
                    XDocument nuspecDoc      = pair.Value;

                    // Does it reference any file from this projects directory structure?
                    // Does it reference any file from the projects build output?
                    bool anyFileReferenced = false;
                    foreach (XElement fileElement in nuspecDoc.Descendants("file"))
                    {
                        XAttribute srcAttrib = fileElement.Attribute("src");
                        if (srcAttrib == null)
                        {
                            continue;
                        }

                        string relativePath = srcAttrib.Value;
                        string relativePathWithoutWildcards = relativePath.Split('*')[0];
                        string filePathBase = Path.Combine(nuspecFileDir, relativePathWithoutWildcards);
                        if (IsPathLocatedIn(filePathBase, info.ProjectRootDir))
                        {
                            anyFileReferenced = true;
                            break;
                        }

                        foreach (string buildOutputPath in info.OutputFilePaths)
                        {
                            bool pathEquals = string.Equals(
                                Path.GetFullPath(filePathBase),
                                Path.GetFullPath(buildOutputPath),
                                StringComparison.InvariantCultureIgnoreCase);
                            if (pathEquals)
                            {
                                anyFileReferenced = true;
                                break;
                            }
                        }
                        if (anyFileReferenced)
                        {
                            break;
                        }
                    }

                    // Skip unrelated projects
                    if (!anyFileReferenced)
                    {
                        continue;
                    }

                    // If it does, it probably belongs to this project. Retrieve some data and stop searching.
                    XElement elemId           = nuspecDoc.Descendants("id").FirstOrDefault();
                    XElement elemVersion      = nuspecDoc.Descendants("version").FirstOrDefault();
                    XElement elemDependencies = nuspecDoc.Descendants("dependencies").FirstOrDefault();
                    info.NuSpecFilePath     = nuspecFilePath;
                    info.NuSpecPackageId    = elemId.Value.Trim();
                    info.NuSpecVersion      = Version.Parse(elemVersion.Value.Trim());
                    info.NuSpecDependencies = (elemDependencies != null) ?
                                              elemDependencies.Elements("dependency").Attributes("id").Select(a => a.Value).ToList() :
                                              new List <string>();
                    break;
                }

                // If we failed to associate the project with a NuSpec, discard it
                if (info.NuSpecPackageId == null)
                {
                    continue;
                }
                if (info.NuSpecVersion == null)
                {
                    continue;
                }

                // Otherwise, keep it for later
                projectInfoList.Add(info);
            }

            // Read the installer package config to flag packages as default
            if (File.Exists(config.InstallerPackageConfigPath))
            {
                XDocument installerConfig = XDocument.Load(config.InstallerPackageConfigPath);
                XElement  elemPackages    = installerConfig.Descendants("Packages").FirstOrDefault();
                foreach (XElement elemPackageItem in elemPackages.Elements("Package"))
                {
                    XAttribute attribId = elemPackageItem.Attribute("id");
                    if (attribId == null)
                    {
                        continue;
                    }

                    string id = attribId.Value;
                    foreach (ProjectInfo projectInfo in projectInfoList)
                    {
                        // Note: Could be multiple projects being used in a single nuspec!
                        if (projectInfo.NuSpecPackageId == id)
                        {
                            projectInfo.IsDefaultPackage = true;
                        }
                    }
                }
            }

            projectInfoList.Sort((a, b) => string.Compare(a.NuSpecPackageId, b.NuSpecPackageId));
            return(projectInfoList);
        }