Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/Elastic.Codex/Sourcing/CodexCloneService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ public class CodexCloneService(ILoggerFactory logFactory, ILinkIndexReader linkI
try
{
var git = new CodexGitRepository(loggerFactory, context.Collector, subDir);
if (!git.HasHead())
{
logger.LogWarning("Could not read commit for {Name}; skipping", repoName);
continue;
}
currentCommit = git.GetCurrentCommit();
}
catch (OperationCanceledException)
Expand Down Expand Up @@ -186,7 +191,10 @@ bool assumeCloned

try
{
var git = new CodexGitRepository(logFactory, context.Collector, repoDir);
// Git command failures emit collector errors. Use a local collector so a
// missing clone token policy warns and skips instead of failing the job.
var gitCollector = new DiagnosticsCollector([]);
var git = new CodexGitRepository(logFactory, gitCollector, repoDir);

if (assumeCloned && git.IsInitialized())
{
Expand Down
39 changes: 24 additions & 15 deletions src/Elastic.Codex/Sourcing/CodexGitRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,25 +53,34 @@ protected override void OnBeforeRetry() =>

public bool IsInitialized() => Directory.Exists(Path.Join(WorkingDirectory.FullName, ".git"));

public void Fetch(string reference) =>
_ = ExecInWithRetry(
EnvironmentVars,
NetworkRetry,
"git",
"fetch",
"--no-tags",
"--prune",
"--no-recurse-submodules",
"--depth",
"1",
"origin",
reference
);
public void Fetch(string reference)
{
if (
!ExecInWithRetry(
EnvironmentVars,
NetworkRetry,
"git",
"fetch",
"--no-tags",
"--prune",
"--no-recurse-submodules",
"--depth",
"1",
"origin",
reference
)
)
throw new InvalidOperationException($"git fetch failed for '{reference}'");
}

public void EnableSparseCheckout(string[] folders) =>
ExecIn(EnvironmentVars, "git", ["sparse-checkout", "set", "--no-cone", .. folders]);

public void Checkout(string reference) => ExecIn(EnvironmentVars, "git", "checkout", "--force", reference);
public void Checkout(string reference)
{
if (!ExecInWithRetry(EnvironmentVars, RetryPolicy.None, "git", "checkout", "--force", reference))
throw new InvalidOperationException($"git checkout failed for '{reference}'");
}

public void GitAddOrigin(string origin) => ExecIn(EnvironmentVars, "git", "remote", "add", "origin", origin);
}
77 changes: 77 additions & 0 deletions tests/Navigation.Tests/Codex/CodexGitRepositoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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 System.IO.Abstractions.TestingHelpers;
using AwesomeAssertions;
using Elastic.Codex.Sourcing;
using Elastic.Documentation.Diagnostics;
using Elastic.Documentation.ExternalCommands;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace Elastic.Documentation.Navigation.Tests.Codex;

public class CodexGitRepositoryTests
{
[Fact]
public void Fetch_WhenGitFails_ThrowsWithoutLeavingTheFailureAsAnUncaughtJobError()
{
var gitCollector = new DiagnosticsCollector([]);
var git = CreateGit(gitCollector, ExitCode(128), ExitCode(128), ExitCode(128));

var act = () => git.Fetch("abc123");

act.Should().Throw<InvalidOperationException>().WithMessage("git fetch failed for 'abc123'");
gitCollector.Errors.Should().Be(1);
}

[Fact]
public void Checkout_WhenGitFails_ThrowsAfterRecordingTheFailureOnTheGitCollector()
{
var gitCollector = new DiagnosticsCollector([]);
var git = CreateGit(gitCollector, ExitCode(1));

var act = () => git.Checkout("FETCH_HEAD");

act.Should().Throw<InvalidOperationException>().WithMessage("git checkout failed for 'FETCH_HEAD'");
gitCollector.Errors.Should().Be(1);
}

private static Func<int> ExitCode(int code) => () => code;

private static ScriptedCodexGitRepository CreateGit(IDiagnosticsCollector gitCollector, params Func<int>[] steps)
{
var fileSystem = new MockFileSystem();
var workingDirectory = fileSystem.DirectoryInfo.New("/tmp/clone/repo");
workingDirectory.Create();
return new ScriptedCodexGitRepository(gitCollector, workingDirectory, steps);
}

private sealed class ScriptedCodexGitRepository(
IDiagnosticsCollector collector,
IDirectoryInfo workingDirectory,
Func<int>[] steps
) : CodexGitRepository(NullLoggerFactory.Instance, collector, workingDirectory)
{
private int _callCount;

protected override int ExecInCore(
Dictionary<string, string> environmentVars,
TimeSpan? attemptTimeout,
string binary,
params string[] args
)
{
if (_callCount >= steps.Length)
throw new InvalidOperationException($"Unexpected invocation {_callCount + 1}");
return steps[_callCount++]();
}

protected override void DelayBeforeRetry(TimeSpan delay)
{
// Tests must not wait on the production 5s fetch back-off.
}
}
}
1 change: 1 addition & 0 deletions tests/Navigation.Tests/Navigation.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

<ItemGroup>
<ProjectReference Include="..\..\src\Elastic.Codex\Elastic.Codex.csproj"/>
<ProjectReference Include="..\..\src\Elastic.Documentation.Tooling\Elastic.Documentation.Tooling.csproj"/>
<ProjectReference Include="..\..\src\Elastic.Documentation.Configuration\Elastic.Documentation.Configuration.csproj"/>
<ProjectReference Include="..\..\src\Elastic.Documentation.Navigation\Elastic.Documentation.Navigation.csproj"/>
<ProjectReference Include="..\..\src\Elastic.Documentation.ServiceDefaults\Elastic.Documentation.ServiceDefaults.csproj" />
Expand Down
Loading