diff --git a/src/Management/src/GitProperties.Build/PackageReadme.md b/src/Management/src/GitProperties.Build/PackageReadme.md
index 4e3115adeb..1c8cb70ef1 100644
--- a/src/Management/src/GitProperties.Build/PackageReadme.md
+++ b/src/Management/src/GitProperties.Build/PackageReadme.md
@@ -102,6 +102,31 @@ This command writes an extra copy of `git.properties` directly next to your proj
>
> If you deploy by pushing your source code directly, rather than a pre-built or published output (for example with Cloud Foundry's `cf push`), be careful not to *also* exclude `git.properties` from whatever gets pushed or deployed. For Cloud Foundry, that means leaving it out of `.cfignore`. `git.properties` must stay out of Git through `.gitignore`, but it still needs to be present on disk and travel along with your source code.
+### Refreshing many projects at once
+
+The command above works for one project at a time. If your solution has several projects, and only some of them use this package, running the same command on a project that doesn't use it fails with an error like this:
+
+```
+error MSB4057: The target "WriteGitPropertiesFallbackFile" does not exist in the project.
+```
+
+To safely refresh every project at once, add a `Directory.Build.targets` file that applies to all of them (your solution's root folder works well). It defines a new target that only calls the real one on projects that use this package. This makes it safe to run on every project, even ones that don't use this package:
+
+```xml
+
+
+
+
+
+
+```
+
+Then, instead of the command shown above, run this new target for your whole solution:
+
+```shell
+dotnet build YourSolution.slnx -t:RefreshGitPropertiesFallbackFile
+```
+
## Using this package in a shared project
Installing this package as shown under [Getting started](#getting-started), whether with the `dotnet` CLI or Visual Studio's Add Package dialog, writes `PrivateAssets="all"` into the `` line automatically. This stops the reference from becoming transitive, so `git.properties` generation stays local to the project you installed it in. That's the right choice for most solutions: usually only a few host apps need `git.properties`, so keeping the reference non-transitive avoids running Git commands anywhere else.
diff --git a/src/Management/src/GitProperties.Build/buildTransitive/Steeltoe.Management.GitProperties.Build.targets b/src/Management/src/GitProperties.Build/buildTransitive/Steeltoe.Management.GitProperties.Build.targets
index a4c9ff0afa..7da08792ec 100644
--- a/src/Management/src/GitProperties.Build/buildTransitive/Steeltoe.Management.GitProperties.Build.targets
+++ b/src/Management/src/GitProperties.Build/buildTransitive/Steeltoe.Management.GitProperties.Build.targets
@@ -58,6 +58,10 @@
$(IntermediateOutputPath)git.properties
+
+ true
+
+
<_GitPropertiesShouldGenerate>$(GenerateGitProperties)
@@ -72,7 +76,7 @@
<_GitPropertiesSuppressGitRepositoryNotFound>false
- <_GitPropertiesSuppressGitRepositoryNotFound Condition="Exists('$(GitPropertiesFallbackFile)')">true
+ <_GitPropertiesSuppressGitRepositoryNotFound Condition="Exists('$(GitPropertiesFallbackFile)') and '$(_ForceWriteGitPropertiesFallbackFile)' != 'true'">true
- <_GitPropertiesFallbackFileTarget Condition="'$(GitPropertiesWriteToProjectDirectory)' == 'true'">$(GitPropertiesFallbackFile)
+ <_GitPropertiesFallbackFileTarget Condition="'$(GitPropertiesWriteToProjectDirectory)' == 'true' or '$(_ForceWriteGitPropertiesFallbackFile)' == 'true'">$(GitPropertiesFallbackFile)
+ Properties="Configuration=$(Configuration);_ForceWriteGitPropertiesFallbackFile=true" />
+
+
+
+
+
+
+ %(ProjectReference.GlobalPropertiesToRemove);_ForceWriteGitPropertiesFallbackFile
+
+
diff --git a/src/Management/test/GitProperties.Build.Test/FallbackFile/WriteGitPropertiesFallbackFileDoesNotForceServiceDefaultsToWriteFallbackCopyTest.cs b/src/Management/test/GitProperties.Build.Test/FallbackFile/WriteGitPropertiesFallbackFileDoesNotForceServiceDefaultsToWriteFallbackCopyTest.cs
new file mode 100644
index 0000000000..5f951e47f5
--- /dev/null
+++ b/src/Management/test/GitProperties.Build.Test/FallbackFile/WriteGitPropertiesFallbackFileDoesNotForceServiceDefaultsToWriteFallbackCopyTest.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information.
+
+namespace Steeltoe.Management.GitProperties.Build.Test.FallbackFile;
+
+public sealed class WriteGitPropertiesFallbackFileDoesNotForceServiceDefaultsToWriteFallbackCopyTest : GitPropertiesTestBase
+{
+ [Fact]
+ public async Task Test()
+ {
+ GitRepository repository = await Workspace.CreateGitRepositoryAsync("repo", 1);
+
+ TestProject serviceDefaults = await repository.AddTestLibraryAsync("ServiceDefaults", true, [
+ Workspace.FakeEndpointPackageReference,
+ Workspace.GetGitPropertiesPackageReferenceWithPrivateAssets("none")
+ ]);
+
+ TestProject apiService = await repository.AddTestAppAsync("ApiService", projectReferences: [serviceDefaults]);
+ await apiService.BuildAsync("-t:WriteGitPropertiesFallbackFile");
+
+ apiService.FallbackGitPropertiesGenerated.Should().BeTrue();
+ serviceDefaults.GitPropertiesGenerated.Should().BeTrue();
+ serviceDefaults.FallbackGitPropertiesGenerated.Should().BeFalse();
+ }
+}
diff --git a/src/Management/test/GitProperties.Build.Test/FallbackFile/WriteGitPropertiesFallbackFileOverridesGlobalOptOutTest.cs b/src/Management/test/GitProperties.Build.Test/FallbackFile/WriteGitPropertiesFallbackFileOverridesGlobalOptOutTest.cs
new file mode 100644
index 0000000000..a282a9cbe3
--- /dev/null
+++ b/src/Management/test/GitProperties.Build.Test/FallbackFile/WriteGitPropertiesFallbackFileOverridesGlobalOptOutTest.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information.
+
+namespace Steeltoe.Management.GitProperties.Build.Test.FallbackFile;
+
+public sealed class WriteGitPropertiesFallbackFileOverridesGlobalOptOutTest : GitPropertiesTestBase
+{
+ [Fact]
+ public async Task Test()
+ {
+ GitRepository repository = await Workspace.CreateGitRepositoryAsync("repo", 1);
+
+ await Workspace.WriteFileAsync(Path.Combine(Workspace.GetPath("repo"), "Directory.Build.props"), """
+
+
+ false
+
+
+ """);
+
+ TestProject testApp = repository.TestApp;
+
+ await testApp.BuildAsync("-t:WriteGitPropertiesFallbackFile");
+
+ testApp.FallbackGitPropertiesGenerated.Should().BeTrue();
+ }
+}
diff --git a/src/Management/test/GitProperties.Build.Test/FallbackFile/WriteGitPropertiesFallbackFileWrapperSupportsServiceDefaultsAndAppHostTest.cs b/src/Management/test/GitProperties.Build.Test/FallbackFile/WriteGitPropertiesFallbackFileWrapperSupportsServiceDefaultsAndAppHostTest.cs
new file mode 100644
index 0000000000..acc3bfae0d
--- /dev/null
+++ b/src/Management/test/GitProperties.Build.Test/FallbackFile/WriteGitPropertiesFallbackFileWrapperSupportsServiceDefaultsAndAppHostTest.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the Apache 2.0 License.
+// See the LICENSE file in the project root for more information.
+
+namespace Steeltoe.Management.GitProperties.Build.Test.FallbackFile;
+
+public sealed class WriteGitPropertiesFallbackFileWrapperSupportsServiceDefaultsAndAppHostTest : GitPropertiesTestBase
+{
+ [Fact]
+ public async Task Test()
+ {
+ GitRepository repository = await Workspace.CreateGitRepositoryAsync("repo", 1);
+
+ await Workspace.WriteFileAsync(Path.Combine(Workspace.GetPath("repo"), "Directory.Build.targets"), """
+
+
+
+
+
+ """);
+
+ TestProject serviceDefaults = await repository.AddTestLibraryAsync("ServiceDefaults", false, [
+ Workspace.FakeEndpointPackageReference,
+ Workspace.GetGitPropertiesPackageReferenceWithPrivateAssets("none")
+ ]);
+
+ TestProject apiService = await repository.AddTestAppAsync("ApiService", projectReferences: [serviceDefaults]);
+ await apiService.BuildAsync("-t:RefreshGitPropertiesFallbackFile");
+
+ TestProject appHost = await repository.AddTestAppAsync("AppHost");
+ await appHost.BuildAsync("-t:RefreshGitPropertiesFallbackFile");
+
+ serviceDefaults.FallbackGitPropertiesGenerated.Should().BeFalse();
+ apiService.FallbackGitPropertiesGenerated.Should().BeTrue();
+ appHost.FallbackGitPropertiesGenerated.Should().BeFalse();
+ }
+}
diff --git a/src/Management/test/GitProperties.Build.Test/GitRepositoryBuilder.cs b/src/Management/test/GitProperties.Build.Test/GitRepositoryBuilder.cs
index c9b796df8a..273b43d292 100644
--- a/src/Management/test/GitProperties.Build.Test/GitRepositoryBuilder.cs
+++ b/src/Management/test/GitProperties.Build.Test/GitRepositoryBuilder.cs
@@ -6,6 +6,14 @@ namespace Steeltoe.Management.GitProperties.Build.Test;
internal static class GitRepositoryBuilder
{
+ private const string GitConfigAppendix = """
+ [user]
+ name = Test User
+ email = test@example.com
+ [commit]
+ gpgsign = false # Avoid an interactive prompt on a machine that has commit signing configured globally.
+ """;
+
private static readonly HashSet DirectoryNamesExcludedInPush = new(StringComparer.OrdinalIgnoreCase)
{
".git",
@@ -17,13 +25,12 @@ public static async Task InitializeEmptyAsync(string destination)
{
Directory.CreateDirectory(destination);
await ProcessRunner.RunGitAsync(destination, "init", "--quiet", "--initial-branch=main", ".");
+ await File.AppendAllTextAsync(Path.Combine(destination, ".git", "config"), GitConfigAppendix, TestContext.Current.CancellationToken);
}
public static async Task InitializeAsync(string destination, int commitCount, bool includeFallbackFileInGitignore)
{
await InitializeEmptyAsync(destination);
- await ProcessRunner.RunGitAsync(destination, "config", "user.name", "Test User");
- await ProcessRunner.RunGitAsync(destination, "config", "user.email", "test@example.com");
string gitignoreContent = includeFallbackFileInGitignore
? """