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); 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; } ProjectInfo dependency = allProjects.FirstOrDefault(p => string.Equals(p.NuSpecPackageId, idAttrib.Value, StringComparison.InvariantCultureIgnoreCase)); if (dependency == null) { continue; } versionAttrib.Value = string.Format("{0}.{1}.{2}", dependency.NuSpecVersion.Major, dependency.NuSpecVersion.Minor, dependency.NuSpecVersion.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(); }