private void UpdateVersion(string projectDirectory, Repository repository, SemanticVersion oldVersion, SemanticVersion newVersion)
        {
            Requires.NotNull(projectDirectory, nameof(projectDirectory));
            Requires.NotNull(repository, nameof(repository));

            var signature      = this.GetSignature(repository);
            var versionOptions = VersionFile.GetVersion(repository, projectDirectory);

            if (IsVersionDecrement(oldVersion, newVersion))
            {
                this.stderr.WriteLine($"Cannot change version from {oldVersion} to {newVersion} because {newVersion} is older than {oldVersion}.");
                throw new ReleasePreparationException(ReleasePreparationError.VersionDecrement);
            }

            if (!EqualityComparer <SemanticVersion> .Default.Equals(versionOptions.Version, newVersion))
            {
                if (versionOptions.VersionHeightPosition.HasValue && GitExtensions.WillVersionChangeResetVersionHeight(versionOptions.Version, newVersion, versionOptions.VersionHeightPosition.Value))
                {
                    // The version will be reset by this change, so remove the version height offset property.
                    versionOptions.VersionHeightOffset = null;
                }

                versionOptions.Version = newVersion;
                var filePath = VersionFile.SetVersion(projectDirectory, versionOptions, includeSchemaProperty: true);

                Commands.Stage(repository, filePath);

                // Author a commit only if we effectively changed something.
                if (!repository.Head.Tip.Tree.Equals(repository.Index.WriteToTree()))
                {
                    repository.Commit($"Set version to '{versionOptions.Version}'", signature, signature, new CommitOptions()
                    {
                        AllowEmptyCommit = false
                    });
                }
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Tests whether a commit is of a specified version, comparing major and minor components
        /// with the version.txt file defined by that commit.
        /// </summary>
        /// <param name="commit">The commit to test.</param>
        /// <param name="expectedVersion">The version to test for in the commit</param>
        /// <param name="comparisonPrecision">The last component of the version to include in the comparison.</param>
        /// <param name="repoRelativeProjectDirectory">The repo-relative directory from which <paramref name="expectedVersion"/> was originally calculated.</param>
        /// <returns><c>true</c> if the <paramref name="commit"/> matches the major and minor components of <paramref name="expectedVersion"/>.</returns>
        internal static bool CommitMatchesVersion(this Commit commit, SemanticVersion expectedVersion, SemanticVersion.Position comparisonPrecision, string repoRelativeProjectDirectory)
        {
            Requires.NotNull(commit, nameof(commit));
            Requires.NotNull(expectedVersion, nameof(expectedVersion));

            var commitVersionData = VersionFile.GetVersion(commit, repoRelativeProjectDirectory);
            var semVerFromFile    = commitVersionData?.Version;

            if (semVerFromFile == null)
            {
                return(false);
            }

            // If the version height position moved, that's an automatic reset in version height.
            if (commitVersionData.VersionHeightPosition != comparisonPrecision)
            {
                return(false);
            }

            if (comparisonPrecision == SemanticVersion.Position.Prerelease)
            {
                // The entire version spec must match exactly.
                return(semVerFromFile?.Equals(expectedVersion) ?? false);
            }

            for (SemanticVersion.Position position = SemanticVersion.Position.Major; position <= comparisonPrecision; position++)
            {
                int expectedValue = ReadVersionPosition(expectedVersion.Version, position);
                int actualValue   = ReadVersionPosition(semVerFromFile.Version, position);
                if (expectedValue != actualValue)
                {
                    return(false);
                }
            }

            return(true);
        }
Exemplo n.º 3
0
        /// <summary>
        /// Encodes a commit from history in a <see cref="Version"/>
        /// so that the original commit can be found later.
        /// </summary>
        /// <param name="commit">The commit whose ID and position in history is to be encoded.</param>
        /// <param name="repoRelativeProjectDirectory">The repo-relative project directory for which to calculate the version.</param>
        /// <param name="versionHeight">
        /// The version height, previously calculated by a call to <see cref="GetVersionHeight(Commit, string)"/>
        /// with the same value for <paramref name="repoRelativeProjectDirectory"/>.
        /// </param>
        /// <returns>
        /// A version whose <see cref="Version.Build"/> and
        /// <see cref="Version.Revision"/> components are calculated based on the commit.
        /// </returns>
        /// <remarks>
        /// In the returned version, the <see cref="Version.Build"/> component is
        /// the height of the git commit while the <see cref="Version.Revision"/>
        /// component is the first four bytes of the git commit id (forced to be a positive integer).
        /// </remarks>
        public static Version GetIdAsVersion(this Commit commit, string repoRelativeProjectDirectory = null, int?versionHeight = null)
        {
            var versionOptions = VersionFile.GetVersion(commit, repoRelativeProjectDirectory);

            return(GetIdAsVersionHelper(commit, versionOptions, repoRelativeProjectDirectory, versionHeight));
        }
Exemplo n.º 4
0
        /// <summary>
        /// Initializes a new instance of the <see cref="VersionOracle"/> class.
        /// </summary>
        public VersionOracle(string projectDirectory, LibGit2Sharp.Repository repo, LibGit2Sharp.Commit head, ICloudBuild cloudBuild, int?overrideBuildNumberOffset = null, string projectPathRelativeToGitRepoRoot = null)
        {
            var repoRoot = repo?.Info?.WorkingDirectory?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
            var relativeRepoProjectDirectory = !string.IsNullOrWhiteSpace(repoRoot)
                ? (!string.IsNullOrEmpty(projectPathRelativeToGitRepoRoot)
                    ? projectPathRelativeToGitRepoRoot
                    : projectDirectory.Substring(repoRoot.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
                : null;

            var commit = head ?? repo?.Head.Commits.FirstOrDefault();

            var committedVersion = VersionFile.GetVersion(commit, relativeRepoProjectDirectory);

            var workingVersion = head != null?VersionFile.GetVersion(head, relativeRepoProjectDirectory) : VersionFile.GetVersion(projectDirectory);

            if (overrideBuildNumberOffset.HasValue)
            {
                if (committedVersion != null)
                {
                    committedVersion.BuildNumberOffset = overrideBuildNumberOffset.Value;
                }

                if (workingVersion != null)
                {
                    workingVersion.BuildNumberOffset = overrideBuildNumberOffset.Value;
                }
            }

            this.VersionOptions = committedVersion ?? workingVersion;

            this.GitCommitId   = commit?.Id.Sha ?? cloudBuild?.GitCommitId ?? null;
            this.VersionHeight = CalculateVersionHeight(relativeRepoProjectDirectory, commit, committedVersion, workingVersion);
            this.BuildingRef   = cloudBuild?.BuildingTag ?? cloudBuild?.BuildingBranch ?? repo?.Head.CanonicalName;

            // Override the typedVersion with the special build number and revision components, when available.
            if (repo != null)
            {
                this.Version = GetIdAsVersion(commit, committedVersion, workingVersion, this.VersionHeight);
            }
            else
            {
                this.Version = this.VersionOptions?.Version.Version ?? Version0;
            }

            this.VersionHeightOffset = this.VersionOptions?.BuildNumberOffsetOrDefault ?? 0;

            this.PrereleaseVersion = this.ReplaceMacros(this.VersionOptions?.Version?.Prerelease ?? string.Empty);

            this.CloudBuildNumberOptions = this.VersionOptions?.CloudBuild?.BuildNumberOrDefault ?? VersionOptions.CloudBuildNumberOptions.DefaultInstance;

            if (!string.IsNullOrEmpty(this.BuildingRef) && this.VersionOptions?.PublicReleaseRefSpec?.Length > 0)
            {
                this.PublicRelease = this.VersionOptions.PublicReleaseRefSpec.Any(
                    expr => Regex.IsMatch(this.BuildingRef, expr));
            }
        }
        /// <summary>
        /// Prepares a release for the specified directory by creating a release branch and incrementing the version in the current branch.
        /// </summary>
        /// <exception cref="ReleasePreparationException">Thrown when the release could not be created.</exception>
        /// <param name="projectDirectory">
        /// The path to the directory which may (or its ancestors may) define the version file.
        /// </param>
        /// <param name="releaseUnstableTag">
        /// The prerelease tag to add to the version on the release branch. Pass <c>null</c> to omit/remove the prerelease tag.
        /// The leading hyphen may be specified or omitted.
        /// </param>
        /// <param name="nextVersion">
        /// The next version to save to the version file on the current branch. Pass <c>null</c> to automatically determine the next
        /// version based on the current version and the <c>versionIncrement</c> setting in <c>version.json</c>.
        /// Parameter will be ignored if the current branch is a release branch.
        /// </param>
        /// <param name="versionIncrement">
        /// The increment to apply in order to determine the next version on the current branch.
        /// If specified, value will be used instead of the increment specified in <c>version.json</c>.
        /// Parameter will be ignored if the current branch is a release branch.
        /// </param>
        public void PrepareRelease(string projectDirectory, string releaseUnstableTag = null, Version nextVersion = null, VersionOptions.ReleaseVersionIncrement?versionIncrement = null)
        {
            Requires.NotNull(projectDirectory, nameof(projectDirectory));

            // open the git repository
            var repository = this.GetRepository(projectDirectory);

            if (repository.Info.IsHeadDetached)
            {
                this.stderr.WriteLine("Detached head. Check out a branch first.");
                throw new ReleasePreparationException(ReleasePreparationError.DetachedHead);
            }

            // get the current version
            var versionOptions = VersionFile.GetVersion(projectDirectory);

            if (versionOptions == null)
            {
                this.stderr.WriteLine($"Failed to load version file for directory '{projectDirectory}'.");
                throw new ReleasePreparationException(ReleasePreparationError.NoVersionFile);
            }

            var releaseBranchName  = this.GetReleaseBranchName(versionOptions);
            var originalBranchName = repository.Head.FriendlyName;
            var releaseVersion     = string.IsNullOrEmpty(releaseUnstableTag)
                ? versionOptions.Version.WithoutPrepreleaseTags()
                : versionOptions.Version.SetFirstPrereleaseTag(releaseUnstableTag);

            // check if the current branch is the release branch
            if (string.Equals(originalBranchName, releaseBranchName, StringComparison.OrdinalIgnoreCase))
            {
                this.stdout.WriteLine($"{releaseBranchName} branch advanced from {versionOptions.Version} to {releaseVersion}.");
                this.UpdateVersion(projectDirectory, repository, versionOptions.Version, releaseVersion);
                return;
            }

            var nextDevVersion = this.GetNextDevVersion(versionOptions, nextVersion, versionIncrement);

            // check if the release branch already exists
            if (repository.Branches[releaseBranchName] != null)
            {
                this.stderr.WriteLine($"Cannot create branch '{releaseBranchName}' because it already exists.");
                throw new ReleasePreparationException(ReleasePreparationError.BranchAlreadyExists);
            }

            // create release branch and update version
            var releaseBranch = repository.CreateBranch(releaseBranchName);

            Commands.Checkout(repository, releaseBranch);
            this.UpdateVersion(projectDirectory, repository, versionOptions.Version, releaseVersion);
            this.stdout.WriteLine($"{releaseBranchName} branch now tracks v{releaseVersion} stabilization and release.");

            // update version on main branch
            Commands.Checkout(repository, originalBranchName);
            this.UpdateVersion(projectDirectory, repository, versionOptions.Version, nextDevVersion);
            this.stdout.WriteLine($"{originalBranchName} branch now tracks v{nextDevVersion} development.");

            // Merge release branch back to main branch
            var mergeOptions = new MergeOptions()
            {
                CommitOnSuccess = true,
                MergeFileFavor  = MergeFileFavor.Ours,
            };

            repository.Merge(releaseBranch, this.GetSignature(repository), mergeOptions);
        }
Exemplo n.º 6
0
        /// <summary>
        /// Initializes a new instance of the <see cref="VersionOracle"/> class.
        /// </summary>
        public VersionOracle(string projectDirectory, LibGit2Sharp.Repository repo, LibGit2Sharp.Commit head, ICloudBuild cloudBuild, int?overrideVersionHeightOffset = null, string projectPathRelativeToGitRepoRoot = null)
        {
            var relativeRepoProjectDirectory = projectPathRelativeToGitRepoRoot ?? repo?.GetRepoRelativePath(projectDirectory);

            if (repo is object)
            {
                // If we're particularly git focused, normalize/reset projectDirectory to be the path we *actually* want to look at in case we're being redirected.
                projectDirectory = Path.Combine(repo.Info.WorkingDirectory, relativeRepoProjectDirectory);
            }

            var commit = head ?? repo?.Head.Tip;

            var committedVersion = VersionFile.GetVersion(commit, relativeRepoProjectDirectory);

            var workingVersion = head is object?VersionFile.GetVersion(head, relativeRepoProjectDirectory) : VersionFile.GetVersion(projectDirectory);

            if (overrideVersionHeightOffset.HasValue)
            {
                if (committedVersion != null)
                {
                    committedVersion.VersionHeightOffset = overrideVersionHeightOffset.Value;
                }

                if (workingVersion != null)
                {
                    workingVersion.VersionHeightOffset = overrideVersionHeightOffset.Value;
                }
            }

            this.VersionOptions = committedVersion ?? workingVersion;

            this.GitCommitId   = commit?.Id.Sha ?? cloudBuild?.GitCommitId ?? null;
            this.GitCommitDate = commit?.Author.When;
            this.VersionHeight = CalculateVersionHeight(relativeRepoProjectDirectory, commit, committedVersion, workingVersion);
            this.BuildingRef   = cloudBuild?.BuildingTag ?? cloudBuild?.BuildingBranch ?? repo?.Head.CanonicalName;

            // Override the typedVersion with the special build number and revision components, when available.
            if (repo != null)
            {
                this.Version = GetIdAsVersion(commit, committedVersion, workingVersion, this.VersionHeight);
            }
            else
            {
                this.Version = this.VersionOptions?.Version.Version ?? Version0;
            }

            // get the commit id abbreviation only if the commit id is set
            if (!string.IsNullOrEmpty(this.GitCommitId))
            {
                var gitCommitIdShortFixedLength = this.VersionOptions?.GitCommitIdShortFixedLength ?? VersionOptions.DefaultGitCommitIdShortFixedLength;
                var gitCommitIdShortAutoMinimum = this.VersionOptions?.GitCommitIdShortAutoMinimum ?? 0;
                // get it from the git repository if there is a repository present and it is enabled
                if (repo != null && gitCommitIdShortAutoMinimum > 0)
                {
                    this.GitCommitIdShort = repo.ObjectDatabase.ShortenObjectId(commit, gitCommitIdShortAutoMinimum);
                }
                else
                {
                    this.GitCommitIdShort = this.GitCommitId.Substring(0, gitCommitIdShortFixedLength);
                }
            }

            this.VersionHeightOffset = this.VersionOptions?.VersionHeightOffsetOrDefault ?? 0;

            this.PrereleaseVersion = this.ReplaceMacros(this.VersionOptions?.Version?.Prerelease ?? string.Empty);

            this.CloudBuildNumberOptions = this.VersionOptions?.CloudBuild?.BuildNumberOrDefault ?? VersionOptions.CloudBuildNumberOptions.DefaultInstance;

            if (!string.IsNullOrEmpty(this.BuildingRef) && this.VersionOptions?.PublicReleaseRefSpec?.Count > 0)
            {
                this.PublicRelease = this.VersionOptions.PublicReleaseRefSpec.Any(
                    expr => Regex.IsMatch(this.BuildingRef, expr));
            }
        }
Exemplo n.º 7
0
        /// <summary>
        /// Initializes a new instance of the <see cref="VersionOracle"/> class.
        /// </summary>
        public VersionOracle(string projectDirectory, LibGit2Sharp.Repository repo, LibGit2Sharp.Commit head, ICloudBuild cloudBuild, int?overrideVersionHeightOffset = null, string projectPathRelativeToGitRepoRoot = null)
        {
            var repoRoot = repo?.Info?.WorkingDirectory?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && repoRoot != null && repoRoot.StartsWith("\\") && (repoRoot.Length == 1 || repoRoot[1] != '\\'))
            {
                // We're in a worktree, which libgit2sharp only gives us as a path relative to the root of the assumed drive.
                // Add the drive: to the front of the repoRoot.
                repoRoot = repo.Info.Path.Substring(0, 2) + repoRoot;
            }

            var relativeRepoProjectDirectory = !string.IsNullOrWhiteSpace(repoRoot)
                ? (!string.IsNullOrEmpty(projectPathRelativeToGitRepoRoot)
                    ? projectPathRelativeToGitRepoRoot
                    : projectDirectory.Substring(repoRoot.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
                : null;

            var commit = head ?? repo?.Head.Tip;

            var committedVersion = VersionFile.GetVersion(commit, relativeRepoProjectDirectory);

            var workingVersion = head != null?VersionFile.GetVersion(head, relativeRepoProjectDirectory) : VersionFile.GetVersion(projectDirectory);

            if (overrideVersionHeightOffset.HasValue)
            {
                if (committedVersion != null)
                {
                    committedVersion.VersionHeightOffset = overrideVersionHeightOffset.Value;
                }

                if (workingVersion != null)
                {
                    workingVersion.VersionHeightOffset = overrideVersionHeightOffset.Value;
                }
            }

            this.VersionOptions = committedVersion ?? workingVersion;

            this.GitCommitId   = commit?.Id.Sha ?? cloudBuild?.GitCommitId ?? null;
            this.GitCommitDate = commit?.Author.When;
            this.VersionHeight = CalculateVersionHeight(relativeRepoProjectDirectory, commit, committedVersion, workingVersion);
            this.BuildingRef   = cloudBuild?.BuildingTag ?? cloudBuild?.BuildingBranch ?? repo?.Head.CanonicalName;

            // Override the typedVersion with the special build number and revision components, when available.
            if (repo != null)
            {
                this.Version = GetIdAsVersion(commit, committedVersion, workingVersion, this.VersionHeight);
            }
            else
            {
                this.Version = this.VersionOptions?.Version.Version ?? Version0;
            }

            // get the commit id abbreviation only if the commit id is set
            if (!string.IsNullOrEmpty(this.GitCommitId))
            {
                var gitCommitIdShortFixedLength = this.VersionOptions?.GitCommitIdShortFixedLength ?? VersionOptions.DefaultGitCommitIdShortFixedLength;
                var gitCommitIdShortAutoMinimum = this.VersionOptions?.GitCommitIdShortAutoMinimum ?? 0;
                // get it from the git repository if there is a repository present and it is enabled
                if (repo != null && gitCommitIdShortAutoMinimum > 0)
                {
                    this.GitCommitIdShort = repo.ObjectDatabase.ShortenObjectId(commit, gitCommitIdShortAutoMinimum);
                }
                else
                {
                    this.GitCommitIdShort = this.GitCommitId.Substring(0, gitCommitIdShortFixedLength);
                }
            }

            this.VersionHeightOffset = this.VersionOptions?.VersionHeightOffsetOrDefault ?? 0;

            this.PrereleaseVersion = this.ReplaceMacros(this.VersionOptions?.Version?.Prerelease ?? string.Empty);

            this.CloudBuildNumberOptions = this.VersionOptions?.CloudBuild?.BuildNumberOrDefault ?? VersionOptions.CloudBuildNumberOptions.DefaultInstance;

            if (!string.IsNullOrEmpty(this.BuildingRef) && this.VersionOptions?.PublicReleaseRefSpec?.Length > 0)
            {
                this.PublicRelease = this.VersionOptions.PublicReleaseRefSpec.Any(
                    expr => Regex.IsMatch(this.BuildingRef, expr));
            }
        }