/// <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="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> private static bool CommitMatchesVersion(Commit commit, Version expectedVersion, string repoRelativeProjectDirectory) { var commitVersionData = VersionFile.GetVersion(commit, repoRelativeProjectDirectory); Version majorMinorFromFile = commitVersionData?.Version ?? Version0; return(majorMinorFromFile?.Major == expectedVersion.Major && majorMinorFromFile?.Minor == expectedVersion.Minor && majorMinorFromFile?.Build == expectedVersion.Build && majorMinorFromFile?.Revision == expectedVersion.Revision); }
/// <summary> /// Gets the number of commits in the longest single path between /// the specified commit and the most distant ancestor (inclusive) /// that set the version to the value at <paramref name="commit"/>. /// </summary> /// <param name="commit">The commit to measure the height of.</param> /// <param name="repoRelativeProjectDirectory">The repo-relative project directory for which to calculate the version.</param> /// <returns>The height of the commit. Always a positive integer.</returns> public static int GetVersionHeight(this Commit commit, string repoRelativeProjectDirectory = null) { var baseVersion = VersionFile.GetVersion(commit, repoRelativeProjectDirectory)?.Version ?? Version0; int height = commit.GetHeight(c => CommitMatchesVersion(c, baseVersion, repoRelativeProjectDirectory)); return(height); }
/// <summary> /// Initializes a new instance of the <see cref="VersionOracle"/> class. /// </summary> public VersionOracle(string projectDirectory, LibGit2Sharp.Repository repo, ICloudBuild cloudBuild) { this.cloudBuild = cloudBuild; this.VersionOptions = VersionFile.GetVersion(repo, projectDirectory) ?? VersionFile.GetVersion(projectDirectory); var repoRoot = repo?.Info?.WorkingDirectory?.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); var relativeRepoProjectDirectory = !string.IsNullOrWhiteSpace(repoRoot) ? projectDirectory.Substring(repoRoot.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) : null; var commit = repo?.Head.Commits.FirstOrDefault(); this.GitCommitId = commit?.Id.Sha ?? cloudBuild?.GitCommitId ?? null; this.VersionHeight = repo?.GetVersionHeight(relativeRepoProjectDirectory) ?? 0; this.BuildingRef = cloudBuild?.BuildingTag ?? cloudBuild?.BuildingBranch ?? repo?.Head.CanonicalName; // Override the typedVersion with the special build number and revision components, when available. this.Version = repo?.GetIdAsVersion(relativeRepoProjectDirectory, this.VersionHeight) ?? this.VersionOptions?.Version.Version; this.Version = this.Version ?? new Version(0, 0); this.CloudBuildNumberOptions = this.VersionOptions?.CloudBuild?.BuildNumber ?? new VersionOptions.CloudBuildNumberOptions(); if (!string.IsNullOrEmpty(this.BuildingRef) && this.VersionOptions?.PublicReleaseRefSpec?.Length > 0) { this.PublicRelease = this.VersionOptions.PublicReleaseRefSpec.Any( expr => Regex.IsMatch(this.BuildingRef, expr)); } }
/// <summary> /// Tests whether a commit's version-spec matches a given version-spec. /// </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, Version 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); } for (SemanticVersion.Position position = SemanticVersion.Position.Major; position <= comparisonPrecision; position++) { int expectedValue = ReadVersionPosition(expectedVersion, position); int actualValue = ReadVersionPosition(semVerFromFile.Version, position); if (expectedValue != actualValue) { return(false); } } return(true); }
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)) { 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 }); } } }
/// <summary> /// Gets the number of commits in the longest single path between /// the specified commit and the most distant ancestor (inclusive) /// that set the version to the value at <paramref name="commit"/>. /// </summary> /// <param name="commit">The commit to measure the height of.</param> /// <param name="repoRelativeProjectDirectory">The repo-relative project directory for which to calculate the version.</param> /// <param name="baseVersion">Optional base version to calculate the height. If not specified, the base version will be calculated by scanning the repository.</param> /// <returns>The height of the commit. Always a positive integer.</returns> public static int GetVersionHeight(this Commit commit, string repoRelativeProjectDirectory = null, Version baseVersion = null) { Requires.NotNull(commit, nameof(commit)); Requires.Argument(repoRelativeProjectDirectory == null || !Path.IsPathRooted(repoRelativeProjectDirectory), nameof(repoRelativeProjectDirectory), "Path should be relative to repo root."); var versionOptions = VersionFile.GetVersion(commit, repoRelativeProjectDirectory); if (versionOptions == null) { return(0); } var baseSemVer = baseVersion != null?SemanticVersion.Parse(baseVersion.ToString()) : versionOptions.Version ?? SemVer0; var versionHeightPosition = versionOptions.VersionHeightPosition; if (versionHeightPosition.HasValue) { int height = commit.GetHeight(c => CommitMatchesVersion(c, baseSemVer, versionHeightPosition.Value, repoRelativeProjectDirectory)); return(height); } return(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="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> private static bool CommitMatchesMajorMinorVersion(Commit commit, Version expectedVersion, string repoRelativeProjectDirectory) { Requires.NotNull(commit, nameof(commit)); Requires.NotNull(expectedVersion, nameof(expectedVersion)); Version majorMinorFromFile = VersionFile.GetVersion(commit, repoRelativeProjectDirectory)?.Version ?? Version0; return(majorMinorFromFile?.Major == expectedVersion.Major && majorMinorFromFile?.Minor == expectedVersion.Minor); }
/// <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) { Requires.NotNull(commit, nameof(commit)); Requires.Argument(repoRelativeProjectDirectory == null || !Path.IsPathRooted(repoRelativeProjectDirectory), nameof(repoRelativeProjectDirectory), "Path should be relative to repo root."); var versionOptions = VersionFile.GetVersion(commit, repoRelativeProjectDirectory); return(GetIdAsVersionHelper(commit, versionOptions, repoRelativeProjectDirectory, versionHeight)); }
/// <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)); } }
internal VersionOptions GetVersion(Commit commit) { if (!this.commitVersionCache.TryGetValue(commit.Id, out VersionOptions options)) { options = VersionFile.GetVersion(commit, this.RepoRelativeDirectory); this.commitVersionCache.Add(commit.Id, options); } return(options); }
/// <summary> /// Gets the number of commits in the longest single path between /// the specified commit and the most distant ancestor (inclusive) /// that set the version to the value at <paramref name="commit"/>. /// </summary> /// <param name="commit">The commit to measure the height of.</param> /// <param name="repoRelativeProjectDirectory">The repo-relative project directory for which to calculate the version.</param> /// <returns>The height of the commit. Always a positive integer.</returns> public static int GetVersionHeight(this Commit commit, string repoRelativeProjectDirectory = null) { Requires.NotNull(commit, nameof(commit)); Requires.Argument(repoRelativeProjectDirectory == null || !Path.IsPathRooted(repoRelativeProjectDirectory), nameof(repoRelativeProjectDirectory), "Path should be relative to repo root."); var baseVersion = VersionFile.GetVersion(commit, repoRelativeProjectDirectory)?.Version?.Version ?? Version0; int height = commit.GetHeight(c => CommitMatchesMajorMinorVersion(c, baseVersion, repoRelativeProjectDirectory)); return(height); }
/// <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="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, Version expectedVersion, string repoRelativeProjectDirectory) { Requires.NotNull(commit, nameof(commit)); Requires.NotNull(expectedVersion, nameof(expectedVersion)); var commitVersionData = VersionFile.GetVersion(commit, repoRelativeProjectDirectory); var semVerFromFile = commitVersionData?.Version; return(semVerFromFile?.Contains(expectedVersion) ?? false); }
/// <summary> /// Looks up the commits that match a specified version number. /// </summary> /// <param name="repo">The repository to search for a matching commit.</param> /// <param name="version">The version previously obtained from <see cref="GetIdAsVersion(Commit, string, int?)"/>.</param> /// <param name="repoRelativeProjectDirectory">The repo-relative project directory from which <paramref name="version"/> was originally calculated.</param> /// <returns>The matching commits, or an empty enumeration if no match is found.</returns> public static IEnumerable <Commit> GetCommitsFromVersion(this Repository repo, Version version, string repoRelativeProjectDirectory = null) { Requires.NotNull(repo, nameof(repo)); Requires.NotNull(version, nameof(version)); var possibleCommits = from commit in GetCommitsReachableFromRefs(repo).Distinct() let commitVersionOptions = VersionFile.GetVersion(commit, repoRelativeProjectDirectory) where commitVersionOptions != null where !IsCommitIdMismatch(version, commitVersionOptions, commit) where !IsVersionHeightMismatch(version, commitVersionOptions, commit, repoRelativeProjectDirectory) select commit; return(possibleCommits); }
/// <summary> /// Gets the version options from HEAD and the working copy (if applicable), /// and tests their equality. /// </summary> /// <param name="repo">The repo to scan for version info.</param> /// <param name="repoRelativeProjectDirectory">The path to the directory of the project whose version is being queried, relative to the repo root.</param> /// <param name="committedVersion">Receives the version options from the HEAD commit.</param> /// <param name="workingCopyVersion">Receives the version options from the working copy, when applicable.</param> /// <returns><c>true</c> if <paramref name="committedVersion"/> and <paramref name="workingCopyVersion"/> are not equal.</returns> private static bool IsVersionFileChangedInWorkingCopy(Repository repo, string repoRelativeProjectDirectory, out VersionOptions committedVersion, out VersionOptions workingCopyVersion) { Commit headCommit = repo.Head.Commits.FirstOrDefault(); committedVersion = VersionFile.GetVersion(headCommit, repoRelativeProjectDirectory); if (!repo.Info.IsBare) { string fullDirectory = Path.Combine(repo.Info.WorkingDirectory, repoRelativeProjectDirectory ?? string.Empty); workingCopyVersion = VersionFile.GetVersion(fullDirectory); return(!EqualityComparer <VersionOptions> .Default.Equals(workingCopyVersion, committedVersion)); } workingCopyVersion = null; return(false); }
/// <summary> /// Looks up the commits that match a specified version number. /// </summary> /// <param name="repo">The repository to search for a matching commit.</param> /// <param name="version">The version previously obtained from <see cref="GetIdAsVersion(Commit, string, int?)"/>.</param> /// <param name="repoRelativeProjectDirectory">The repo-relative project directory from which <paramref name="version"/> was originally calculated.</param> /// <returns>The matching commits, or an empty enumeration if no match is found.</returns> public static IEnumerable <Commit> GetCommitsFromVersion(this Repository repo, Version version, string repoRelativeProjectDirectory = null) { // The revision is a 16-bit unsigned integer, but is not allowed to be 0xffff. // So if the value is 0xfffe, consider that the actual last bit is insignificant // since the original git commit ID could have been either 0xffff or 0xfffe. ushort objectIdLeadingValue = (ushort)version.Revision; ushort objectIdMask = (ushort)(version.Revision == MaximumBuildNumberOrRevisionComponent ? 0xfffe : 0xffff); var possibleCommits = from commit in GetCommitsReachableFromRefs(repo) where version.Revision == -1 || commit.Id.StartsWith(objectIdLeadingValue, objectIdMask) let buildNumberOffset = VersionFile.GetVersion(commit, repoRelativeProjectDirectory)?.BuildNumberOffset ?? 0 let versionHeight = commit.GetHeight(c => CommitMatchesVersion(c, version, repoRelativeProjectDirectory)) where versionHeight == version.Build - buildNumberOffset select commit; return(possibleCommits); }
internal VersionOptions GetVersion(Commit commit) { if (!this.commitVersionCache.TryGetValue(commit.Id, out VersionOptions options)) { try { options = VersionFile.GetVersion(commit, this.RepoRelativeDirectory, this.blobVersionCache); } catch (Exception ex) { throw new InvalidOperationException($"Unable to get version from commit: {commit.Id.Sha}", ex); } this.commitVersionCache.Add(commit.Id, options); } return(options); }
/// <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> /// <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) { Requires.NotNull(commit, nameof(commit)); Requires.Argument(repoRelativeProjectDirectory == null || !Path.IsPathRooted(repoRelativeProjectDirectory), nameof(repoRelativeProjectDirectory), "Path should be relative to repo root."); var baseVersion = VersionFile.GetVersion(commit, repoRelativeProjectDirectory)?.Version ?? Version0; // The compiler (due to WinPE header requirements) only allows 16-bit version components, // and forbids 0xffff as a value. // The build number is set to the git height. This helps ensure that // within a major.minor release, each patch has an incrementing integer. // The revision is set to the first two bytes of the git commit ID. int build = commit.GetHeight(c => CommitMatchesMajorMinorVersion(c, baseVersion, repoRelativeProjectDirectory)); Verify.Operation(build <= MaximumBuildNumberOrRevisionComponent, "Git height is {0}, which is greater than the maximum allowed {0}.", build, MaximumBuildNumberOrRevisionComponent); int revision = Math.Min(MaximumBuildNumberOrRevisionComponent, commit.GetTruncatedCommitIdAsUInt16()); return(new Version(baseVersion.Major, baseVersion.Minor, build, revision)); }
/// <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); }
/// <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)); } }
/// <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)); } }
/// <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)); }
/// <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); }