From 98e682f16bd94723479c4e5b4ee9f1067de9a4e8 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 26 Mar 2025 15:05:20 +0100 Subject: [PATCH 1/2] stage --- src/docs-assembler/assembler.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/docs-assembler/assembler.yml b/src/docs-assembler/assembler.yml index 2a2ebd094..43be38abf 100644 --- a/src/docs-assembler/assembler.yml +++ b/src/docs-assembler/assembler.yml @@ -2,6 +2,7 @@ environments: prod: uri: https://www.elastic.co path_prefix: docs + content_source: current # current | next allow_indexing: false google_tag_manager: enabled: true @@ -9,6 +10,7 @@ environments: staging: uri: https://staging-website.elastic.co path_prefix: docs + content_source: next google_tag_manager: enabled: true id: GTM-KNJMG2M @@ -17,12 +19,36 @@ environments: cookies_win: x preview: uri: https://docs-v3-preview.elastic.dev + content_source: next path_prefix: dev: uri: http://localhost:4000 + content_source: next path_prefix: docs + +### +# 'narrative' shares the same keys as keys in 'references' () +# 'narrative' defines the docs-content repository +### narrative: checkout_strategy: full + +### +# 'references' defines a map of `elastic/ * +# repository_config: +# skip: bool +# # skip repository from cloning and building. +# checkout_strategy: full | partial +# # 'full' git clone --depth-1 --single-branch +# # 'partial' --cone sparse-checkout of only the 'docs' folder with --filter=blob:none +# current: +# # defines +# # defaults to 'main' +# next: +# # defaults to 'main' +# +### + references: apm-server: apm-agent-android: From 2aee9146a2e1ebe8c1dcd7d66e814f126a6c5fd4 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Tue, 1 Apr 2025 17:23:54 +0200 Subject: [PATCH 2/2] Add current/next configuration to assembler.yml --- src/Elastic.Markdown/Elastic.Markdown.csproj | 2 +- .../Myst/YamlSerialization.cs | 1 - src/docs-assembler/AssembleContext.cs | 10 +- src/docs-assembler/Cli/RepositoryCommands.cs | 13 +- .../Configuration/AssemblyConfiguration.cs | 27 +++- .../Configuration/Repository.cs | 5 +- .../Navigation/AssemblerDocumentationSet.cs | 2 +- .../Sourcing/RepositorySourcesFetcher.cs | 143 ++++++++++++------ src/docs-assembler/YamlStaticContext.cs | 1 + src/docs-assembler/assembler.yml | 13 +- src/docs-assembler/docs-assembler.csproj | 5 + .../AssemblerConfigurationTests.cs | 64 ++++++++ 12 files changed, 225 insertions(+), 61 deletions(-) create mode 100644 tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs diff --git a/src/Elastic.Markdown/Elastic.Markdown.csproj b/src/Elastic.Markdown/Elastic.Markdown.csproj index 6ef4b4150..e71ea3b59 100644 --- a/src/Elastic.Markdown/Elastic.Markdown.csproj +++ b/src/Elastic.Markdown/Elastic.Markdown.csproj @@ -59,7 +59,7 @@ - + diff --git a/src/Elastic.Markdown/Myst/YamlSerialization.cs b/src/Elastic.Markdown/Myst/YamlSerialization.cs index 1ae8e1ca6..9cdf134db 100644 --- a/src/Elastic.Markdown/Myst/YamlSerialization.cs +++ b/src/Elastic.Markdown/Myst/YamlSerialization.cs @@ -39,5 +39,4 @@ public static T Deserialize(string yaml) [YamlSerializable(typeof(Setting))] [YamlSerializable(typeof(AllowedValue))] [YamlSerializable(typeof(SettingMutability))] - public partial class DocsBuilderYamlStaticContext; diff --git a/src/docs-assembler/AssembleContext.cs b/src/docs-assembler/AssembleContext.cs index 50afdec10..836f574c8 100644 --- a/src/docs-assembler/AssembleContext.cs +++ b/src/docs-assembler/AssembleContext.cs @@ -60,13 +60,15 @@ public AssembleContext( ExtractAssemblerConfiguration(navigationPath, "navigation.yml"); NavigationPath = ReadFileSystem.FileInfo.New(navigationPath); - CheckoutDirectory = ReadFileSystem.DirectoryInfo.New(checkoutDirectory ?? ".artifacts/checkouts"); - OutputDirectory = ReadFileSystem.DirectoryInfo.New(output ?? ".artifacts/assembly"); - - if (!Configuration.Environments.TryGetValue(environment, out var env)) throw new Exception($"Could not find environment {environment}"); Environment = env; + + var contentSource = Environment.ContentSource.ToStringFast(); + CheckoutDirectory = ReadFileSystem.DirectoryInfo.New(checkoutDirectory ?? Path.Combine(".artifacts", "checkouts", contentSource)); + OutputDirectory = ReadFileSystem.DirectoryInfo.New(output ?? Path.Combine(".artifacts", "assembly")); + + } private void ExtractAssemblerConfiguration(string configPath, string file) diff --git a/src/docs-assembler/Cli/RepositoryCommands.cs b/src/docs-assembler/Cli/RepositoryCommands.cs index 66f0553f4..0bc41b56c 100644 --- a/src/docs-assembler/Cli/RepositoryCommands.cs +++ b/src/docs-assembler/Cli/RepositoryCommands.cs @@ -28,15 +28,22 @@ private void AssignOutputLogger() // libgit2 is magnitudes slower to clone repositories https://github.com/libgit2/libgit2/issues/4674 /// Clones all repositories /// Treat warnings as errors and fail the build on warnings + /// The environment to build /// [Command("clone-all")] - public async Task CloneAll(bool? strict = null, Cancel ctx = default) + public async Task CloneAll( + bool? strict = null, + string? environment = null, + Cancel ctx = default + ) { AssignOutputLogger(); + var githubEnvironmentInput = githubActionsService.GetInput("environment"); + environment ??= !string.IsNullOrEmpty(githubEnvironmentInput) ? githubEnvironmentInput : "dev"; await using var collector = new ConsoleDiagnosticsCollector(logger, githubActionsService); - var assembleContext = new AssembleContext("dev", collector, new FileSystem(), new FileSystem(), null, null); + var assembleContext = new AssembleContext(environment, collector, new FileSystem(), new FileSystem(), null, null); var cloner = new AssemblerRepositorySourcer(logger, assembleContext); _ = await cloner.AcquireAllLatest(ctx); @@ -49,7 +56,7 @@ public async Task CloneAll(bool? strict = null, Cancel ctx = default) /// Force a full rebuild of the destination folder /// Treat warnings as errors and fail the build on warnings /// Allow indexing and following of html files - /// The environment to resolve links to + /// The environment to build /// [Command("build-all")] public async Task BuildAll( diff --git a/src/docs-assembler/Configuration/AssemblyConfiguration.cs b/src/docs-assembler/Configuration/AssemblyConfiguration.cs index 383f863e4..36cd68281 100644 --- a/src/docs-assembler/Configuration/AssemblyConfiguration.cs +++ b/src/docs-assembler/Configuration/AssemblyConfiguration.cs @@ -2,6 +2,8 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.ComponentModel.DataAnnotations; +using NetEscapades.EnumGenerators; using YamlDotNet.Serialization; namespace Documentation.Assembler.Configuration; @@ -24,6 +26,7 @@ public static AssemblyConfiguration Deserialize(string yaml) var repository = RepositoryDefaults(r, name); config.ReferenceRepositories[name] = repository; } + foreach (var (name, env) in config.Environments) env.Name = name; config.Narrative = RepositoryDefaults(config.Narrative, NarrativeRepository.RepositoryName); @@ -44,8 +47,10 @@ private static TRepository RepositoryDefaults(TRepository r, string var repository = r ?? new TRepository(); // ReSharper restore NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract repository.Name = name; - if (string.IsNullOrEmpty(repository.CurrentBranch)) - repository.CurrentBranch = "main"; + if (string.IsNullOrEmpty(repository.GitReferenceCurrent)) + repository.GitReferenceCurrent = "main"; + if (string.IsNullOrEmpty(repository.GitReferenceNext)) + repository.GitReferenceNext = "main"; if (string.IsNullOrEmpty(repository.Origin)) { if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"))) @@ -70,6 +75,9 @@ private static TRepository RepositoryDefaults(TRepository r, string [YamlMember(Alias = "environments")] public Dictionary Environments { get; set; } = []; + + [YamlMember(Alias = "named_git_references")] + public Dictionary NamedGitReferences { get; set; } = []; } public record PublishEnvironment @@ -86,16 +94,30 @@ public record PublishEnvironment [YamlMember(Alias = "allow_indexing")] public bool AllowIndexing { get; set; } + [YamlMember(Alias = "content_source")] + public ContentSource ContentSource { get; set; } + [YamlMember(Alias = "google_tag_manager")] public GoogleTagManager GoogleTagManager { get; set; } = new(); } +[EnumExtensions] +public enum ContentSource +{ + [Display(Name = "next")] + Next, + + [Display(Name = "current")] + Current +} + public record GoogleTagManager { [YamlMember(Alias = "enabled")] public bool Enabled { get; set; } private string _id = string.Empty; + [YamlMember(Alias = "id")] public string Id { @@ -107,6 +129,7 @@ public string Id _id = value; } } + [YamlMember(Alias = "auth")] public string? Auth { get; set; } diff --git a/src/docs-assembler/Configuration/Repository.cs b/src/docs-assembler/Configuration/Repository.cs index 46ad8462f..1d783e0af 100644 --- a/src/docs-assembler/Configuration/Repository.cs +++ b/src/docs-assembler/Configuration/Repository.cs @@ -21,7 +21,10 @@ public record Repository public string Origin { get; set; } = string.Empty; [YamlMember(Alias = "current")] - public string CurrentBranch { get; set; } = "main"; + public string GitReferenceCurrent { get; set; } = "main"; + + [YamlMember(Alias = "next")] + public string GitReferenceNext { get; set; } = "main"; [YamlMember(Alias = "checkout_strategy")] public string CheckoutStrategy { get; set; } = "partial"; diff --git a/src/docs-assembler/Navigation/AssemblerDocumentationSet.cs b/src/docs-assembler/Navigation/AssemblerDocumentationSet.cs index 9ee97b7a4..459cd2cf2 100644 --- a/src/docs-assembler/Navigation/AssemblerDocumentationSet.cs +++ b/src/docs-assembler/Navigation/AssemblerDocumentationSet.cs @@ -45,7 +45,7 @@ TableOfContentsTreeCollector treeCollector RepositoryName = checkout.Repository.Name, Ref = checkout.HeadReference, Remote = $"elastic/${checkout.Repository.Name}", - Branch = checkout.Repository.CurrentBranch + Branch = checkout.Repository.GitReferenceCurrent }; var buildContext = new BuildContext( diff --git a/src/docs-assembler/Sourcing/RepositorySourcesFetcher.cs b/src/docs-assembler/Sourcing/RepositorySourcesFetcher.cs index 8468604bd..35c0216c9 100644 --- a/src/docs-assembler/Sourcing/RepositorySourcesFetcher.cs +++ b/src/docs-assembler/Sourcing/RepositorySourcesFetcher.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO.Abstractions; using Documentation.Assembler.Configuration; using Elastic.Markdown.IO; @@ -18,6 +19,7 @@ public class AssemblerRepositorySourcer(ILoggerFactory logger, AssembleContext c private readonly ILogger _logger = logger.CreateLogger(); private AssemblyConfiguration Configuration => context.Configuration; + private PublishEnvironment PublishEnvironment => context.Environment; public IReadOnlyCollection GetAll() { @@ -27,20 +29,28 @@ public IReadOnlyCollection GetAll() foreach (var repo in repositories) { var checkoutFolder = fs.DirectoryInfo.New(Path.Combine(context.CheckoutDirectory.FullName, repo.Name)); - //var head = Capture(checkoutFolder, "git", "rev-parse", "HEAD"); var checkout = new Checkout { Repository = repo, Directory = checkoutFolder, + //TODO read from links.json and ensure we check out exactly that git reference + //+ validate that git reference belongs to the appropriate branch HeadReference = Guid.NewGuid().ToString("N") }; checkouts.Add(checkout); } + return checkouts; } public async Task> AcquireAllLatest(Cancel ctx = default) { + _logger.LogInformation( + "Cloning all repositories for environment {EnvironmentName} using '{ContentSourceStrategy}' content sourcing strategy", + PublishEnvironment.Name, + PublishEnvironment.ContentSource.ToStringFast() + ); + var dict = new ConcurrentDictionary(); var checkouts = new ConcurrentBag(); @@ -65,7 +75,7 @@ await Task.Run(() => }).ConfigureAwait(false); foreach (var kv in dict.OrderBy(kv => kv.Value.Elapsed)) - _logger.LogInformation("-> {Repository}\ttook: {Elapsed}", kv.Key, kv.Value.Elapsed); + _logger.LogInformation("-> took: {Elapsed}\t{RepositoryBranch}", kv.Key, kv.Value.Elapsed); return checkouts.ToList().AsReadOnly(); } @@ -76,39 +86,20 @@ private Checkout CloneOrUpdateRepository(Repository repository, string name, Con var checkoutFolder = fs.DirectoryInfo.New(Path.Combine(context.CheckoutDirectory.FullName, name)); var relativePath = Path.GetRelativePath(Paths.WorkingDirectoryRoot.FullName, checkoutFolder.FullName); var sw = Stopwatch.StartNew(); - _ = dict.AddOrUpdate(name, sw, (_, _) => sw); - var head = string.Empty; + var branch = PublishEnvironment.ContentSource == ContentSource.Next + ? repository.GitReferenceNext + : repository.GitReferenceCurrent; + + _ = dict.AddOrUpdate($"{name} ({branch})", sw, (_, _) => sw); + + string? head; if (checkoutFolder.Exists) { - _logger.LogInformation("Pull: {Name}\t{Repository}\t{RelativePath}", name, repository, relativePath); - // --allow-unrelated-histories due to shallow clones not finding a common ancestor - ExecIn(checkoutFolder, "git", "pull", "--depth", "1", "--allow-unrelated-histories", "--no-ff"); - //head = Capture(checkoutFolder, "git", "rev-parse", "HEAD"); - head = Guid.NewGuid().ToString("N"); + if (!TryUpdateSource(name, branch, relativePath, checkoutFolder, out head)) + head = CheckoutFromScratch(repository, name, branch, relativePath, checkoutFolder); } else - { - _logger.LogInformation("Checkout: {Name}\t{Repository}\t{RelativePath}", name, repository, relativePath); - if (repository.CheckoutStrategy == "full") - { - Exec("git", "clone", repository.Origin, checkoutFolder.FullName, - "--depth", "1", "--single-branch", - "--branch", repository.CurrentBranch - ); - } - else if (repository.CheckoutStrategy == "partial") - { - Exec( - "git", "clone", "--filter=blob:none", "--no-checkout", repository.Origin, checkoutFolder.FullName - ); - - ExecIn(checkoutFolder, "git", "sparse-checkout", "set", "--cone"); - ExecIn(checkoutFolder, "git", "checkout", repository.CurrentBranch); - ExecIn(checkoutFolder, "git", "sparse-checkout", "set", "docs"); - //head = Capture(checkoutFolder, "git", "rev-parse", "HEAD"); - head = Guid.NewGuid().ToString("N"); - } - } + head = CheckoutFromScratch(repository, name, branch, relativePath, checkoutFolder); sw.Stop(); @@ -120,6 +111,55 @@ private Checkout CloneOrUpdateRepository(Repository repository, string name, Con }; } + private bool TryUpdateSource(string name, string branch, string relativePath, IDirectoryInfo checkoutFolder, [NotNullWhen(true)] out string? head) + { + head = null; + try + { + _logger.LogInformation("Pull: {Name}\t{Branch}\t{RelativePath}", name, branch, relativePath); + // --allow-unrelated-histories due to shallow clones not finding a common ancestor + ExecIn(checkoutFolder, "git", "pull", "--depth", "1", "--allow-unrelated-histories", "--no-ff"); + head = Capture(checkoutFolder, "git", "rev-parse", "HEAD"); + return true; + } + catch (Exception e) + { + _logger.LogError(e, "Failed to update {Name} from {RelativePath}, falling back to recreating from scratch", name, relativePath); + if (checkoutFolder.Exists) + { + checkoutFolder.Delete(true); + checkoutFolder.Refresh(); + } + } + + return false; + } + + private string CheckoutFromScratch(Repository repository, string name, string branch, string relativePath, + IDirectoryInfo checkoutFolder) + { + _logger.LogInformation("Checkout: {Name}\t{Branch}\t{RelativePath}", name, branch, relativePath); + if (repository.CheckoutStrategy == "full") + { + Exec("git", "clone", repository.Origin, checkoutFolder.FullName, + "--depth", "1", "--single-branch", + "--branch", branch + ); + } + else if (repository.CheckoutStrategy == "partial") + { + Exec( + "git", "clone", "--filter=blob:none", "--no-checkout", repository.Origin, checkoutFolder.FullName + ); + + ExecIn(checkoutFolder, "git", "sparse-checkout", "set", "--cone"); + ExecIn(checkoutFolder, "git", "checkout", branch); + ExecIn(checkoutFolder, "git", "sparse-checkout", "set", "docs"); + } + + return Capture(checkoutFolder, "git", "rev-parse", "HEAD"); + } + private void Exec(string binary, params string[] args) => ExecIn(null, binary, args); private void ExecIn(IDirectoryInfo? workingDirectory, string binary, params string[] args) @@ -136,19 +176,36 @@ private void ExecIn(IDirectoryInfo? workingDirectory, string binary, params stri // ReSharper disable once UnusedMember.Local private string Capture(IDirectoryInfo? workingDirectory, string binary, params string[] args) { - var arguments = new StartArguments(binary, args) + // Try 10 times to capture the output of the command, if it fails we'll throw an exception on the last try + for (var i = 0; i < 9; i++) { - WorkingDirectory = workingDirectory?.FullName, - //WaitForStreamReadersTimeout = TimeSpan.FromSeconds(3), - Timeout = TimeSpan.FromSeconds(3), - WaitForExit = TimeSpan.FromSeconds(3), - ConsoleOutWriter = NoopConsoleWriter.Instance - }; - var result = Proc.Start(arguments); - if (result.ExitCode != 0) - context.Collector.EmitError("", $"Exit code: {result.ExitCode} while executing {binary} {string.Join(" ", args)} in {workingDirectory}"); - var line = result.ConsoleOut.FirstOrDefault()?.Line ?? throw new Exception($"No output captured for {binary}: {workingDirectory}"); - return line; + try + { + return CaptureOutput(); + } + catch + { + // ignored + } + } + return CaptureOutput(); + + string CaptureOutput() + { + var arguments = new StartArguments(binary, args) + { + WorkingDirectory = workingDirectory?.FullName, + //WaitForStreamReadersTimeout = TimeSpan.FromSeconds(3), + Timeout = TimeSpan.FromSeconds(3), + WaitForExit = TimeSpan.FromSeconds(3), + ConsoleOutWriter = NoopConsoleWriter.Instance + }; + var result = Proc.Start(arguments); + if (result.ExitCode != 0) + context.Collector.EmitError("", $"Exit code: {result.ExitCode} while executing {binary} {string.Join(" ", args)} in {workingDirectory}"); + var line = result.ConsoleOut.FirstOrDefault()?.Line ?? throw new Exception($"No output captured for {binary}: {workingDirectory}"); + return line; + } } } diff --git a/src/docs-assembler/YamlStaticContext.cs b/src/docs-assembler/YamlStaticContext.cs index 7fff8253d..e2963ece4 100644 --- a/src/docs-assembler/YamlStaticContext.cs +++ b/src/docs-assembler/YamlStaticContext.cs @@ -13,4 +13,5 @@ namespace Documentation.Assembler; [YamlSerializable(typeof(NarrativeRepository))] [YamlSerializable(typeof(PublishEnvironment))] [YamlSerializable(typeof(GoogleTagManager))] +[YamlSerializable(typeof(ContentSource))] public partial class YamlStaticContext; diff --git a/src/docs-assembler/assembler.yml b/src/docs-assembler/assembler.yml index 2d26ad88f..dbf6fba7b 100644 --- a/src/docs-assembler/assembler.yml +++ b/src/docs-assembler/assembler.yml @@ -22,6 +22,10 @@ environments: content_source: next path_prefix: docs +named_git_references: + stack: &stack 9.0 + cloud-hosted: ms-120 + ### # 'narrative' shares the same keys as keys in 'references' () # 'narrative' defines the docs-content repository @@ -33,16 +37,11 @@ narrative: # 'references' defines a map of `elastic/ * # repository_config: # skip: bool -# # skip repository from cloning and building. # checkout_strategy: full | partial # # 'full' git clone --depth-1 --single-branch # # 'partial' --cone sparse-checkout of only the 'docs' folder with --filter=blob:none # current: -# # defines -# # defaults to 'main' # next: -# # defaults to 'main' -# ### references: @@ -63,10 +62,13 @@ references: cloud-on-k8s: cloud: current: master + next: master curator: current: master + next: master ecctl: current: master + next: master ecs-dotnet: ecs-logging-go-logrus: ecs-logging-go-zap: @@ -91,6 +93,7 @@ references: # skip: true elasticsearch-ruby: elasticsearch: + current: *stack go-elasticsearch: integrations: kibana: diff --git a/src/docs-assembler/docs-assembler.csproj b/src/docs-assembler/docs-assembler.csproj index ec1ec216d..8f5af3db4 100644 --- a/src/docs-assembler/docs-assembler.csproj +++ b/src/docs-assembler/docs-assembler.csproj @@ -22,6 +22,11 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs b/tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs new file mode 100644 index 000000000..b88b2bd66 --- /dev/null +++ b/tests/docs-assembler.Tests/src/docs-assembler.Tests/AssemblerConfigurationTests.cs @@ -0,0 +1,64 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.IO.Abstractions; +using Documentation.Assembler.Configuration; +using Elastic.Markdown.Diagnostics; +using Elastic.Markdown.IO; +using FluentAssertions; + +namespace Documentation.Assembler.Tests; + +public class AssemblerConfigurationTests +{ + private DiagnosticsCollector Collector { get; } + private AssembleContext Context { get; } + private FileSystem FileSystem { get; } + private IDirectoryInfo CheckoutDirectory { get; set; } + public AssemblerConfigurationTests() + { + FileSystem = new FileSystem(); + CheckoutDirectory = FileSystem.DirectoryInfo.New( + FileSystem.Path.Combine(Paths.GetSolutionDirectory()!.FullName, ".artifacts", "checkouts") + ); + Collector = new DiagnosticsCollector([]); + Context = new AssembleContext("dev", Collector, FileSystem, FileSystem, CheckoutDirectory.FullName, null); + } + + [Fact] + public void ReadsContentSource() + { + var environments = Context.Configuration.Environments; + environments.Should().NotBeEmpty() + .And.ContainKey("prod"); + + var prod = environments["prod"]; + prod.ContentSource.Should().Be(ContentSource.Current); + + var staging = environments["staging"]; + staging.ContentSource.Should().Be(ContentSource.Next); + } + + [Fact] + public void ReadsVersions() + { + var config = Context.Configuration; + config.NamedGitReferences.Should().NotBeEmpty() + .And.ContainKey("stack"); + + config.NamedGitReferences["stack"].Should().NotBeNullOrEmpty(); + + var agent = config.ReferenceRepositories["elasticsearch"]; + agent.GitReferenceCurrent.Should().NotBeNullOrEmpty() + .And.Be(config.NamedGitReferences["stack"]); + + // test defaults + var apmServer = config.ReferenceRepositories["apm-server"]; + apmServer.GitReferenceNext.Should().NotBeNullOrEmpty() + .And.Be("main"); + apmServer.GitReferenceCurrent.Should().NotBeNullOrEmpty() + .And.Be("main"); + + } +}