Skip to content

Commit

Permalink
Fixes for git head in tests microsoft#1801 (microsoft#1802)
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite authored Apr 20, 2024
1 parent bf5536f commit 2c81463
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 75 deletions.
2 changes: 2 additions & 0 deletions docs/CHANGELOG-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ What's changed since pre-release v3.0.0-B0153:
- Bug fixes:
- Fixed discovery of installed modules in CLI by @BernieWhite.
[#1779](https://github.com/microsoft/PSRule/issues/1779)
- Fixed for git head in tests by @BernieWhite.
[#1801](https://github.com/microsoft/PSRule/issues/1801)

## v3.0.0-B0153 (pre-release)

Expand Down
11 changes: 11 additions & 0 deletions docs/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ The following execution options have been deprecated and will be removed from _v
You do not need to configure both options.
If you have the deprecated option configured, switch to the new option.

### Git Head input object

Previously when the `Input.Format` option was set to `File` the `.git/HEAD` file was emitted as an input object.
The original purpose of this feature was to allow conventions to run once against the root of the repository.
Subsequent changes to PSRule have made this feature redundant by adding support for the `-Initialize` block.

From _v3_ the `.git/HEAD` file will no longer be emitted as an input object.

Consider adding or updating a convention that uses the `-Initialize` block to emit run initialization logic.
Yon can also use the `-Initialize` block to emit a custom object to the pipeline by using the `$PSRule.ImportWithType` method.

### Rule output object

Several properties of the rule object have been renamed to improve consistency with other objects.
Expand Down
9 changes: 3 additions & 6 deletions docs/upgrade-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,15 @@ To resolve any issue caused by this change, you can:
[1]: https://aka.ms/ps-rule/options#bindingtargetname
[2]: https://aka.ms/ps-rule/options#suppression

### Using PowerShell 7.3 or later
### Using PowerShell 7.4 or later

From _v3.0.0_, PSRule requires:

- Windows PowerShell 5.1 for running as a PowerShell module. _OR_
- PowerShell 7.3 or later for development, building locally, or running as a PowerShell module.
- PowerShell 7.4 or later for development, building locally, or running as a PowerShell module.

Support for Windows PowerShell 5.1 is deprecated and will be removed in a future release of PSRule (v4).
We recommend upgrading to PowerShell 7.3 or later.

At the time of writing, PowerShell 7.3 is the most recent version of PowerShell.
PowerShell 7.4 is expected, and PSRule v4 will aim to support the latest version of PowerShell as the minimum requirement.
We recommend upgrading to PowerShell 7.4 or later.

### Changes to CLI commands

Expand Down
90 changes: 67 additions & 23 deletions src/PSRule/Common/GitHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ internal static class GitHelper
{
private const string GIT_HEAD = "HEAD";
private const string GIT_REF_PREFIX = "ref: ";
private const string GIT_DEFAULT_PATH = ".git/";
private const string GIT_GITDIR_PREFIX = "gitdir: ";
private const string GIT_DEFAULT_PATH = ".git";
private const string GIT_REF_HEAD = "refs/heads/";

private const string GITHUB_BASE_URL = "https://github.com/";
Expand Down Expand Up @@ -44,7 +45,7 @@ internal static class GitHelper
/// <summary>
/// The get HEAD ref.
/// </summary>
public static bool TryHeadRef(out string value, string path = null)
public static bool TryHeadRef(out string value)
{
// Try PSRule
if (Environment.TryString(ENV_PSRULE_REPO_REF, out value) ||
Expand All @@ -61,22 +62,22 @@ public static bool TryHeadRef(out string value, string path = null)
return true;

// Try .git/
return TryReadHead(path, out value);
return TryReadHead(out value);
}

/// <summary>
/// Get the HEAD branch name.
/// </summary>
public static bool TryHeadBranch(out string value, string path = null)
public static bool TryHeadBranch(out string value)
{
value = TryHeadRef(out value, path) && value.StartsWith(GIT_REF_HEAD) ? value.Substring(11) : value;
value = TryHeadRef(out value) && value.StartsWith(GIT_REF_HEAD) ? value.Substring(11) : value;
return !string.IsNullOrEmpty(value);
}

/// <summary>
/// Get the target ref.
/// </summary>
public static bool TryBaseRef(out string value, string path = null)
public static bool TryBaseRef(out string value)
{
// Try PSRule
if (Environment.TryString(ENV_PSRULE_REPO_BASEREF, out value))
Expand All @@ -91,10 +92,10 @@ public static bool TryBaseRef(out string value, string path = null)
return true;

// Try .git/
return TryReadHead(path, out value);
return TryReadHead(out value);
}

public static bool TryRevision(out string value, string path = null)
public static bool TryRevision(out string value)
{
// Try PSRule
if (Environment.TryString(ENV_PSRULE_REPO_REVISION, out value))
Expand All @@ -109,7 +110,7 @@ public static bool TryRevision(out string value, string path = null)
return true;

// Try .git/
return TryReadCommit(path, out value);
return TryReadCommit(out value);
}

public static bool TryRepository(out string value, string path = null)
Expand Down Expand Up @@ -151,6 +152,14 @@ public static bool TryGetChangedFiles(string baseRef, string filter, string opti
return true;
}

#region Helper methods

internal static bool TryReadHead(out string value, string path = null)
{
value = null;
return TryGitFile(GIT_HEAD, out var filePath, path) && TryReadRef(filePath, out value, out _);
}

private static string GetGitBinary()
{
return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ||
Expand All @@ -162,43 +171,76 @@ private static string GetDiffArgs(string target, string source, string filter, s
return $"diff --diff-filter={filter} --ignore-submodules=all --name-only --no-renames {target}";
}

internal static bool TryReadHead(string path, out string value)
private static bool TryReadCommit(out string value)
{
value = null;
return TryGitFile(path, GIT_HEAD, out var filePath) && TryCommit(filePath, out value, out _);
if (!TryGitFile(GIT_HEAD, out var filePath))
return false;

while (TryReadRef(filePath, out value, out var isRef) && isRef)
TryGitFile(value, out filePath);

return value != null;
}

private static bool TryGitFile(string file, out string filePath, string path = null)
{
var gitPath = GetGitDir(path);
filePath = Path.Combine(gitPath, file);
return File.Exists(filePath);
}

internal static bool TryReadCommit(string path, out string value)
private static string GetGitDir(string path)
{
var gitDir = Environment.GetRootedBasePath(path ?? GIT_DEFAULT_PATH);

// Try the case of a submodule.
if (File.Exists(path) && TryReadGitDir(path, out gitDir))
return gitDir;

// Try the simple case of .git/.
return gitDir;
}

private static bool TryReadGitDir(string path, out string value)
{
value = null;
if (!TryGitFile(path, GIT_HEAD, out var filePath))
if (!TryReadFirstLineFromGitFile(path, out var line))
return false;

while (TryCommit(filePath, out value, out var isRef) && isRef)
TryGitFile(path, value, out filePath);
if (!line.StartsWith(GIT_GITDIR_PREFIX, StringComparison.OrdinalIgnoreCase))
return false;

return value != null;
value = Environment.GetRootedBasePath(line.Substring(8));
return true;
}

private static bool TryGitFile(string path, string file, out string filePath)
private static bool TryReadRef(string path, out string value, out bool isRef)
{
filePath = Path.Combine(Environment.GetRootedBasePath(path ?? GIT_DEFAULT_PATH), file);
return File.Exists(filePath);
value = null;
isRef = false;
if (!TryReadFirstLineFromGitFile(path, out var line))
return false;

isRef = line.StartsWith(GIT_REF_PREFIX, StringComparison.OrdinalIgnoreCase);
value = isRef ? line.Substring(5) : line;
return true;
}

private static bool TryCommit(string path, out string value, out bool isRef)
/// <summary>
/// Read the first line of a git file.
/// </summary>
private static bool TryReadFirstLineFromGitFile(string path, out string value)
{
value = null;
isRef = false;
if (!File.Exists(path))
return false;

var lines = File.ReadAllLines(path);
if (lines == null || lines.Length == 0)
return false;

isRef = lines[0].StartsWith(GIT_REF_PREFIX, StringComparison.OrdinalIgnoreCase);
value = isRef ? lines[0].Substring(5) : lines[0];
value = lines[0];
return true;
}

Expand Down Expand Up @@ -235,4 +277,6 @@ private static string GetWorktreeConfigArgs()
{
return "config --worktree --list";
}

#endregion Helper methods
}
9 changes: 1 addition & 8 deletions src/PSRule/Pipeline/GetTargetPipelineBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using PSRule.Configuration;
Expand Down Expand Up @@ -99,13 +99,6 @@ protected override PipelineInputStream PrepareReader()
return PipelineReceiverActions.ConvertFromPowerShellData(sourceObject, next);
});
}
else if (Option.Input.Format == InputFormat.File)
{
AddVisitTargetObjectAction((sourceObject, next) =>
{
return PipelineReceiverActions.ConvertFromGitHead(sourceObject, next);
});
}
else if (Option.Input.Format == InputFormat.Detect && _InputPath != null)
{
AddVisitTargetObjectAction((sourceObject, next) =>
Expand Down
7 changes: 0 additions & 7 deletions src/PSRule/Pipeline/InvokePipelineBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,6 @@ protected override PipelineInputStream PrepareReader()
return PipelineReceiverActions.ConvertFromPowerShellData(sourceObject, next);
});
}
else if (Option.Input.Format == InputFormat.File)
{
AddVisitTargetObjectAction((sourceObject, next) =>
{
return PipelineReceiverActions.ConvertFromGitHead(sourceObject, next);
});
}
else if (Option.Input.Format == InputFormat.Detect && _InputPath != null)
{
AddVisitTargetObjectAction((sourceObject, next) =>
Expand Down
30 changes: 2 additions & 28 deletions src/PSRule/Pipeline/PipelineReciever.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public static IEnumerable<TargetObject> ConvertFromPowerShellData(TargetObject t
{
// Only attempt to deserialize if the input is a string or a file
if (!IsAcceptedType(targetObject))
return new TargetObject[] { targetObject };
return [targetObject];

var data = ReadAsString(targetObject, out var sourceInfo);
var ast = System.Management.Automation.Language.Parser.ParseInput(data, out _, out _);
Expand All @@ -175,16 +175,6 @@ public static IEnumerable<TargetObject> ConvertFromPowerShellData(TargetObject t
return VisitItems(value, sourceInfo, next);
}

public static IEnumerable<TargetObject> ConvertFromGitHead(TargetObject targetObject, VisitTargetObject next)
{
// Only attempt to convert if Git HEAD file
if (!IsGitHead(targetObject))
return new TargetObject[] { targetObject };

var value = PSObject.AsPSObject(GetRepositoryInfo(targetObject, out var sourceInfo));
return VisitItems(new PSObject[] { value }, sourceInfo, next);
}

public static IEnumerable<TargetObject> ReadObjectPath(TargetObject targetObject, VisitTargetObject source, string objectPath, bool caseSensitive)
{
if (!ObjectHelper.GetPath(
Expand All @@ -206,7 +196,7 @@ public static IEnumerable<TargetObject> ReadObjectPath(TargetObject targetObject
}
else
{
return new TargetObject[] { new(PSObject.AsPSObject(nestedObject), targetObject.Source) };
return [new(PSObject.AsPSObject(nestedObject), targetObject.Source)];
}
}

Expand All @@ -232,22 +222,6 @@ targetObject.Value.BaseObject is FileInfo ||
targetObject.Value.BaseObject is Uri;
}

private static bool IsGitHead(TargetObject targetObject)
{
return targetObject.Value.BaseObject is InputFileInfo info && info.DisplayName == InputFileInfo_GitHead;
}

private static RepositoryInfo GetRepositoryInfo(TargetObject targetObject, out TargetSourceInfo sourceInfo)
{
sourceInfo = null;
if (targetObject.Value.BaseObject is not InputFileInfo inputFileInfo)
return null;

sourceInfo = new TargetSourceInfo(inputFileInfo);
GitHelper.TryHeadRef(out var headRef, inputFileInfo.DirectoryName);
return new RepositoryInfo(inputFileInfo.BasePath, headRef);
}

private static string ReadAsString(TargetObject targetObject, out TargetSourceInfo sourceInfo)
{
sourceInfo = null;
Expand Down
15 changes: 12 additions & 3 deletions tests/PSRule.Tests/GitHelperTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.IO;
using System.Runtime.InteropServices;

namespace PSRule;

Expand All @@ -15,7 +15,7 @@ public void TryReadHead_WhenValidPath_ShouldReturnGitHead()
{
var expectedHead = GetGitOutput();

Assert.True(GitHelper.TryReadHead("../../../../../.git/", out var actualHead));
Assert.True(GitHelper.TryReadHead(out var actualHead, "../../../../../.git/"));
Assert.Equal(expectedHead, NormalizeBranch(actualHead));
}

Expand All @@ -28,7 +28,16 @@ private static string NormalizeBranch(string actualHead)

private static string GetGitOutput()
{
return File.ReadAllText("../../../../../.git/HEAD").Replace("ref: ", string.Empty).Replace("refs/heads/", string.Empty).Trim();
var bin = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "git" : "git.exe";
var git = ExternalTool.Get(null, bin);
git.WaitForExit("branch --show-current", out _);
var branch = git.GetOutput().Trim();
if (string.IsNullOrEmpty(branch))
{
git.WaitForExit("rev-parse HEAD", out _);
branch = git.GetOutput().Trim();
}
return branch;
}

#endregion Helper methods
Expand Down

0 comments on commit 2c81463

Please sign in to comment.