private static ExitCodes OnGetVersionCommand(string projectPath, string format, string versionOrRef)
        {
            if (string.IsNullOrEmpty(format))
            {
                format = DefaultVersionInfoFormat;
            }

            if (string.IsNullOrEmpty(versionOrRef))
            {
                versionOrRef = DefaultRef;
            }

            string searchPath = GetSpecifiedOrCurrentDirectoryPath(projectPath);

            var repository = GitExtensions.OpenGitRepo(searchPath);

            if (repository == null)
            {
                Console.Error.WriteLine("No git repo found at or above: \"{0}\"", searchPath);
                return(ExitCodes.NoGitRepo);
            }

            LibGit2Sharp.GitObject refObject = null;
            try
            {
                repository.RevParse(versionOrRef, out var reference, out refObject);
            }
            catch (LibGit2Sharp.NotFoundException) { }

            var commit = refObject as LibGit2Sharp.Commit;

            if (commit == null)
            {
                Console.Error.WriteLine("rev-parse produced no commit for {0}", versionOrRef);
                return(ExitCodes.BadGitRef);
            }

            var oracle = new VersionOracle(searchPath, repository, commit, CloudBuild.Active);

            switch (format.ToLowerInvariant())
            {
            case "text":
                Console.WriteLine("Version:                      {0}", oracle.Version);
                Console.WriteLine("AssemblyVersion:              {0}", oracle.AssemblyVersion);
                Console.WriteLine("AssemblyInformationalVersion: {0}", oracle.AssemblyInformationalVersion);
                Console.WriteLine("NuGet package Version:        {0}", oracle.NuGetPackageVersion);
                Console.WriteLine("NPM package Version:          {0}", oracle.NpmPackageVersion);
                break;

            case "json":
                Console.WriteLine(JsonConvert.SerializeObject(oracle, Formatting.Indented));
                break;

            default:
                Console.Error.WriteLine("Unsupported format: {0}", format);
                return(ExitCodes.UnsupportedFormat);
            }

            return(ExitCodes.OK);
        }
        private static ExitCodes OnGetCommitsCommand(string projectPath, string version, bool quiet)
        {
            if (!Version.TryParse(version, out Version parsedVersion))
            {
                Console.Error.WriteLine($"\"{version}\" is not a simple a.b.c[.d] version spec.");
                return(ExitCodes.InvalidVersionSpec);
            }

            string searchPath = GetSpecifiedOrCurrentDirectoryPath(projectPath);

            var repository = GitExtensions.OpenGitRepo(searchPath);

            if (repository == null)
            {
                Console.Error.WriteLine("No git repo found at or above: \"{0}\"", searchPath);
                return(ExitCodes.NoGitRepo);
            }

            string repoRelativeProjectDir = GetRepoRelativePath(searchPath, repository);
            var    candidateCommits       = GitExtensions.GetCommitsFromVersion(repository, parsedVersion, repoRelativeProjectDir).ToList();

            PrintCommits(quiet, searchPath, repository, candidateCommits);

            return(ExitCodes.OK);
        }
        private static ExitCodes OnSetVersionCommand(string projectPath, string version)
        {
            if (!SemanticVersion.TryParse(string.IsNullOrEmpty(version) ? DefaultVersionSpec : version, out var semver))
            {
                Console.Error.WriteLine($"\"{version}\" is not a semver-compliant version spec.");
                return(ExitCodes.InvalidVersionSpec);
            }

            var defaultOptions = new VersionOptions
            {
                Version = semver,
            };

            string searchPath      = GetSpecifiedOrCurrentDirectoryPath(projectPath);
            var    repository      = GitExtensions.OpenGitRepo(searchPath);
            var    existingOptions = VersionFile.GetVersion(searchPath, out string actualDirectory);
            string versionJsonPath;

            if (existingOptions != null)
            {
                existingOptions.Version = semver;
                versionJsonPath         = VersionFile.SetVersion(actualDirectory, existingOptions);
            }
            else if (string.IsNullOrEmpty(projectPath))
            {
                if (repository == null)
                {
                    Console.Error.WriteLine("No version file and no git repo found at or above: \"{0}\"", searchPath);
                    return(ExitCodes.NoGitRepo);
                }

                versionJsonPath = VersionFile.SetVersion(repository.Info.WorkingDirectory, defaultOptions);
            }
            else
            {
                versionJsonPath = VersionFile.SetVersion(projectPath, defaultOptions);
            }

            if (repository != null)
            {
                LibGit2Sharp.Commands.Stage(repository, versionJsonPath);
            }

            return(ExitCodes.OK);
        }
        private static ExitCodes OnTagCommand(string projectPath, string versionOrRef)
        {
            if (string.IsNullOrEmpty(versionOrRef))
            {
                versionOrRef = DefaultRef;
            }

            string searchPath = GetSpecifiedOrCurrentDirectoryPath(projectPath);

            var repository = GitExtensions.OpenGitRepo(searchPath);

            if (repository == null)
            {
                Console.Error.WriteLine("No git repo found at or above: \"{0}\"", searchPath);
                return(ExitCodes.NoGitRepo);
            }

            LibGit2Sharp.GitObject refObject = null;
            try
            {
                repository.RevParse(versionOrRef, out var reference, out refObject);
            }
            catch (LibGit2Sharp.NotFoundException) { }

            var commit = refObject as LibGit2Sharp.Commit;

            if (commit == null)
            {
                if (!Version.TryParse(versionOrRef, out Version parsedVersion))
                {
                    Console.Error.WriteLine($"\"{versionOrRef}\" is not a simple a.b.c[.d] version spec or git reference.");
                    return(ExitCodes.InvalidVersionSpec);
                }

                string repoRelativeProjectDir = GetRepoRelativePath(searchPath, repository);
                var    candidateCommits       = GitExtensions.GetCommitsFromVersion(repository, parsedVersion, repoRelativeProjectDir).ToList();
                if (candidateCommits.Count == 0)
                {
                    Console.Error.WriteLine("No commit with that version found.");
                    return(ExitCodes.NoMatchingVersion);
                }
                else if (candidateCommits.Count > 1)
                {
                    PrintCommits(false, searchPath, repository, candidateCommits, includeOptions: true);
                    int selection;
                    do
                    {
                        Console.Write("Enter selection: ");
                    }while (!int.TryParse(Console.ReadLine(), out selection) || selection > candidateCommits.Count || selection < 1);
                    commit = candidateCommits[selection - 1];
                }
                else
                {
                    commit = candidateCommits.Single();
                }
            }

            var oracle = new VersionOracle(searchPath, repository, commit, CloudBuild.Active);

            if (!oracle.VersionFileFound)
            {
                Console.Error.WriteLine("No version.json file found in or above \"{0}\" in commit {1}.", searchPath, commit.Sha);
                return(ExitCodes.NoVersionJsonFound);
            }

            oracle.PublicRelease = true; // assume a public release so we don't get a redundant -gCOMMITID in the tag name
            string tagName = $"v{oracle.SemVer2}";

            try
            {
                repository.Tags.Add(tagName, commit);
            }
            catch (LibGit2Sharp.NameConflictException)
            {
                var  taggedCommit = repository.Tags[tagName].Target as LibGit2Sharp.Commit;
                bool correctTag   = taggedCommit?.Sha == commit.Sha;
                Console.Error.WriteLine("The tag {0} is already defined ({1}).", tagName, correctTag ? "to the right commit" : $"expected {commit.Sha} but was on {taggedCommit.Sha}");
                return(correctTag ? ExitCodes.OK : ExitCodes.TagConflict);
            }

            Console.WriteLine("{0} tag created at {1}.", tagName, commit.Sha);
            Console.WriteLine("Remember to push to a remote: git push origin {0}", tagName);

            return(ExitCodes.OK);
        }
        private static ExitCodes OnGetVersionCommand(string projectPath, string format, string singleVariable, string versionOrRef)
        {
            if (string.IsNullOrEmpty(format))
            {
                format = DefaultOutputFormat;
            }

            if (string.IsNullOrEmpty(versionOrRef))
            {
                versionOrRef = DefaultRef;
            }

            string searchPath = GetSpecifiedOrCurrentDirectoryPath(projectPath);

            var repository = GitExtensions.OpenGitRepo(searchPath);

            if (repository == null)
            {
                Console.Error.WriteLine("No git repo found at or above: \"{0}\"", searchPath);
                return(ExitCodes.NoGitRepo);
            }

            LibGit2Sharp.GitObject refObject = null;
            try
            {
                repository.RevParse(versionOrRef, out var reference, out refObject);
            }
            catch (LibGit2Sharp.NotFoundException) { }

            var commit = refObject as LibGit2Sharp.Commit;

            if (commit == null)
            {
                Console.Error.WriteLine("rev-parse produced no commit for {0}", versionOrRef);
                return(ExitCodes.BadGitRef);
            }

            var oracle = new VersionOracle(searchPath, repository, commit, CloudBuild.Active);

            // Take the PublicRelease environment variable into account, since the build would as well.
            if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("PublicRelease")) && bool.TryParse(Environment.GetEnvironmentVariable("PublicRelease"), out bool publicRelease))
            {
                oracle.PublicRelease = publicRelease;
            }

            if (string.IsNullOrEmpty(singleVariable))
            {
                switch (format.ToLowerInvariant())
                {
                case "text":
                    Console.WriteLine("Version:                      {0}", oracle.Version);
                    Console.WriteLine("AssemblyVersion:              {0}", oracle.AssemblyVersion);
                    Console.WriteLine("AssemblyInformationalVersion: {0}", oracle.AssemblyInformationalVersion);
                    Console.WriteLine("NuGetPackageVersion:          {0}", oracle.NuGetPackageVersion);
                    Console.WriteLine("NpmPackageVersion:            {0}", oracle.NpmPackageVersion);
                    break;

                case "json":
                    var converters = new JsonConverter[]
                    {
                        new Newtonsoft.Json.Converters.VersionConverter(),
                    };
                    Console.WriteLine(JsonConvert.SerializeObject(oracle, Formatting.Indented, converters));
                    break;

                default:
                    Console.Error.WriteLine("Unsupported format: {0}", format);
                    return(ExitCodes.UnsupportedFormat);
                }
            }
            else
            {
                if (format != "text")
                {
                    Console.Error.WriteLine("Format must be \"text\" when querying for an individual variable's value.");
                    return(ExitCodes.UnsupportedFormat);
                }

                var property = oracle.GetType().GetProperty(singleVariable, CaseInsensitiveFlags);
                if (property == null)
                {
                    Console.Error.WriteLine("Variable \"{0}\" not a version property.", singleVariable);
                    return(ExitCodes.BadVariable);
                }

                Console.WriteLine(property.GetValue(oracle));
            }

            return(ExitCodes.OK);
        }
        private static ExitCodes OnInstallCommand(string versionJsonRoot, string version)
        {
            if (!SemanticVersion.TryParse(string.IsNullOrEmpty(version) ? DefaultVersionSpec : version, out var semver))
            {
                Console.Error.WriteLine($"\"{version}\" is not a semver-compliant version spec.");
                return(ExitCodes.InvalidVersionSpec);
            }

            var options = new VersionOptions
            {
                Version = semver,
                PublicReleaseRefSpec = new string[]
                {
                    @"^refs/heads/master$",
                    @"^refs/heads/v\d+(?:\.\d+)?$",
                },
                CloudBuild = new VersionOptions.CloudBuildOptions
                {
                    BuildNumber = new VersionOptions.CloudBuildNumberOptions
                    {
                        Enabled = true,
                    },
                },
            };
            string searchPath = GetSpecifiedOrCurrentDirectoryPath(versionJsonRoot);

            if (!Directory.Exists(searchPath))
            {
                Console.Error.WriteLine("\"{0}\" is not an existing directory.", searchPath);
                return(ExitCodes.NoGitRepo);
            }

            var repository = GitExtensions.OpenGitRepo(searchPath);

            if (repository == null)
            {
                Console.Error.WriteLine("No git repo found at or above: \"{0}\"", searchPath);
                return(ExitCodes.NoGitRepo);
            }

            if (string.IsNullOrEmpty(versionJsonRoot))
            {
                versionJsonRoot = repository.Info.WorkingDirectory;
            }

            var existingOptions = VersionFile.GetVersion(versionJsonRoot);

            if (existingOptions != null)
            {
                if (!string.IsNullOrEmpty(version))
                {
                    var setVersionExitCode = OnSetVersionCommand(versionJsonRoot, version);
                    if (setVersionExitCode != ExitCodes.OK)
                    {
                        return(setVersionExitCode);
                    }
                }
            }
            else
            {
                string versionJsonPath = VersionFile.SetVersion(versionJsonRoot, options);
                LibGit2Sharp.Commands.Stage(repository, versionJsonPath);
            }

            // Create/update the Directory.Build.props file in the directory of the version.json file to add the NB.GV package.
            string directoryBuildPropsPath = Path.Combine(versionJsonRoot, "Directory.Build.props");

            MSBuild.Project propsFile;
            if (File.Exists(directoryBuildPropsPath))
            {
                propsFile = new MSBuild.Project(directoryBuildPropsPath);
            }
            else
            {
                propsFile = new MSBuild.Project();
            }

            const string PackageReferenceItemType = "PackageReference";
            const string PackageId = "Nerdbank.GitVersioning";

            if (!propsFile.GetItemsByEvaluatedInclude(PackageId).Any(i => i.ItemType == "PackageReference"))
            {
                string packageVersion = GetLatestPackageVersionAsync(PackageId).GetAwaiter().GetResult();
                propsFile.AddItem(
                    PackageReferenceItemType,
                    PackageId,
                    new Dictionary <string, string>
                {
                    { "Version", packageVersion },     // TODO: use the latest version... somehow...
                    { "PrivateAssets", "all" },
                });

                propsFile.Save(directoryBuildPropsPath);
            }

            LibGit2Sharp.Commands.Stage(repository, directoryBuildPropsPath);

            return(ExitCodes.OK);
        }
        private static ExitCodes OnInstallCommand(string versionJsonRoot, string version, IReadOnlyList <string> sources)
        {
            if (!SemanticVersion.TryParse(string.IsNullOrEmpty(version) ? DefaultVersionSpec : version, out var semver))
            {
                Console.Error.WriteLine($"\"{version}\" is not a semver-compliant version spec.");
                return(ExitCodes.InvalidVersionSpec);
            }

            var options = new VersionOptions
            {
                Version = semver,
                PublicReleaseRefSpec = new string[]
                {
                    @"^refs/heads/master$",
                    @"^refs/heads/v\d+(?:\.\d+)?$",
                },
                CloudBuild = new VersionOptions.CloudBuildOptions
                {
                    BuildNumber = new VersionOptions.CloudBuildNumberOptions
                    {
                        Enabled = true,
                    },
                },
            };
            string searchPath = GetSpecifiedOrCurrentDirectoryPath(versionJsonRoot);

            if (!Directory.Exists(searchPath))
            {
                Console.Error.WriteLine("\"{0}\" is not an existing directory.", searchPath);
                return(ExitCodes.NoGitRepo);
            }

            var repository = GitExtensions.OpenGitRepo(searchPath);

            if (repository == null)
            {
                Console.Error.WriteLine("No git repo found at or above: \"{0}\"", searchPath);
                return(ExitCodes.NoGitRepo);
            }

            if (string.IsNullOrEmpty(versionJsonRoot))
            {
                versionJsonRoot = repository.Info.WorkingDirectory;
            }

            var existingOptions = VersionFile.GetVersion(versionJsonRoot);

            if (existingOptions != null)
            {
                if (!string.IsNullOrEmpty(version))
                {
                    var setVersionExitCode = OnSetVersionCommand(versionJsonRoot, version);
                    if (setVersionExitCode != ExitCodes.OK)
                    {
                        return(setVersionExitCode);
                    }
                }
            }
            else
            {
                string versionJsonPath = VersionFile.SetVersion(versionJsonRoot, options);
                LibGit2Sharp.Commands.Stage(repository, versionJsonPath);
            }

            // Create/update the Directory.Build.props file in the directory of the version.json file to add the NB.GV package.
            string directoryBuildPropsPath = Path.Combine(versionJsonRoot, "Directory.Build.props");

            MSBuild.Project propsFile;
            if (File.Exists(directoryBuildPropsPath))
            {
                propsFile = new MSBuild.Project(directoryBuildPropsPath);
            }
            else
            {
                propsFile = new MSBuild.Project();
            }

            const string PackageReferenceItemType = "PackageReference";

            if (!propsFile.GetItemsByEvaluatedInclude(PackageId).Any(i => i.ItemType == "PackageReference"))
            {
                // Validate given sources
                foreach (var source in sources)
                {
                    if (!Uri.TryCreate(source, UriKind.Absolute, out var _))
                    {
                        Console.Error.WriteLine($"\"{source}\" is not a valid NuGet package source.");
                        return(ExitCodes.InvalidNuGetPackageSource);
                    }
                }

                string packageVersion = GetLatestPackageVersionAsync(PackageId, versionJsonRoot, sources).GetAwaiter().GetResult();
                if (string.IsNullOrEmpty(packageVersion))
                {
                    string verifyPhrase = sources.Any()
                        ? "Please verify the given 'source' option(s)."
                        : "Please verify the package sources in the NuGet.Config files.";
                    Console.Error.WriteLine($"Latest stable version of the {PackageId} package could not be determined. " + verifyPhrase);
                    return(ExitCodes.PackageIdNotFound);
                }

                propsFile.AddItem(
                    PackageReferenceItemType,
                    PackageId,
                    new Dictionary <string, string>
                {
                    { "Version", packageVersion },
                    { "PrivateAssets", "all" },
                });

                propsFile.Save(directoryBuildPropsPath);
            }

            LibGit2Sharp.Commands.Stage(repository, directoryBuildPropsPath);

            return(ExitCodes.OK);
        }