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
11 changes: 11 additions & 0 deletions .autover/changes/fe6f4076-8447-466a-b743-c8dc32123dc7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Projects": [
{
"Name": "AutoVer",
"Type": "Patch",
"ChangelogMessages": [
"Detect linked git worktrees so change files are written to the correct repository root."
]
}
]
}
7 changes: 6 additions & 1 deletion src/AutoVer/Services/GitHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ public string FindGitRootDirectory(string? currentPath)

while (currentPath != null)
{
if (directoryManager.GetDirectories(currentPath, ".git").Any())
// A standard repository has `.git` as a directory. A linked git worktree
// has `.git` as a file containing a `gitdir:` pointer to the actual git
// directory. Treat both as the source-control root so we don't walk past
// the worktree and find the main repository's `.git` directory instead.
if (directoryManager.GetDirectories(currentPath, ".git").Any() ||
fileManager.Exists(Path.Combine(currentPath, ".git")))
{
var sourceControlRootDirectory = directoryManager.GetDirectoryInfo(currentPath).FullName;
_gitRootCache[currentPath] = sourceControlRootDirectory;
Expand Down
114 changes: 114 additions & 0 deletions test/AutoVer.UnitTests/GitWorktreeTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System.Diagnostics;
using AutoVer.UnitTests.Utilities;
using LibGit2Sharp;

namespace AutoVer.UnitTests;

[Retry(3)]
public class GitWorktreeTest
{
[Before(Test)]
public void Before(TestContext context)
{
var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempDir);
Repository.Init(tempDir);
using (var repo = new Repository(tempDir))
{
context.ObjectBag["tempDir"] = repo.Info.WorkingDirectory;
IOUtilities.AddGitignore(repo.Info.WorkingDirectory);
}
}

[Test]
public async Task ChangeCommand_FromLinkedWorktree_WritesChangeFileInsideWorktree()
{
string mainRepoDir = TestContext.Current?.ObjectBag["tempDir"]?.ToString()
?? throw new Exception("Temp directory is null");

// Set up a project in the main repo and commit it so we have something to branch from.
await Assert.That(await IOUtilities.CreateProject(mainRepoDir, "src", "Project1")).IsTrue();
await IOUtilities.SetProjectVersion(Path.Combine(mainRepoDir, "src", "Project1", "Project1.csproj"), "1.0.0");

string autoVerFile =
$@"{{
""Projects"": [
{{
""Name"": ""Project1"",
""Path"": ""src/Project1/Project1.csproj""
}}
],
""UseCommitsForChangelog"": false,
""UseSameVersionForAllProjects"": false,
""DefaultIncrementType"": ""Patch"",
""ChangeFilesDetermineIncrementType"": true
}}";
await IOUtilities.AddAutoVerFile(mainRepoDir, autoVerFile);
GitUtilities.StageChanges(mainRepoDir, "*");
GitUtilities.CommitChanges(mainRepoDir, "Initial Commit");

// Add a linked worktree on a new branch. In a linked worktree `.git` is a file
// (containing a `gitdir:` pointer) rather than a directory — this is the case
// the fix in GitHandler.FindGitRootDirectory targets.
var worktreeDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
await RunGit(mainRepoDir, "worktree", "add", "-b", "feature/wt", worktreeDir);
await Assert.That(File.Exists(Path.Combine(worktreeDir, ".git"))).IsTrue();
await Assert.That(Directory.Exists(Path.Combine(worktreeDir, ".git"))).IsFalse();

// Run `autover change` from inside the worktree.
var app = AutoVerUtilities.InitializeApp();
await Assert.That(app).IsNotNull();
var args = new[]
{
"change",
"--project-path", worktreeDir,
"--project-name", "Project1",
"--increment-type", "Minor",
"-m", "Worktree change"
};
var exitCode = await app!.Run(args);

await Assert.That(exitCode).IsEqualTo(0);

// The change file must be written inside the worktree, NOT in the main repo.
var worktreeChangesDir = Path.Combine(worktreeDir, ".autover", "changes");
var mainRepoChangesDir = Path.Combine(mainRepoDir, ".autover", "changes");

await Assert.That(Directory.Exists(worktreeChangesDir)).IsTrue();
var worktreeChangeFiles = Directory.GetFiles(worktreeChangesDir, "*.json");
await Assert.That(worktreeChangeFiles.Length).IsEqualTo(1);

var mainRepoChangeFiles = Directory.Exists(mainRepoChangesDir)
? Directory.GetFiles(mainRepoChangesDir, "*.json")
: Array.Empty<string>();
await Assert.That(mainRepoChangeFiles.Length).IsEqualTo(0);

var content = await File.ReadAllTextAsync(worktreeChangeFiles[0]);
await Assert.That(content).Contains("Worktree change");
await Assert.That(content).Contains("Project1");
}

private static async Task RunGit(string workingDirectory, params string[] args)
{
var psi = new ProcessStartInfo
{
FileName = "git",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = workingDirectory
};
foreach (var arg in args)
psi.ArgumentList.Add(arg);

using var process = new Process { StartInfo = psi };
process.Start();
var stdout = await process.StandardOutput.ReadToEndAsync();
var stderr = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();

if (process.ExitCode != 0)
throw new Exception($"git {string.Join(' ', args)} failed (exit {process.ExitCode}): {stderr}{stdout}");
}
}
Loading