diff --git a/Directory.Build.props b/Directory.Build.props
index f03de8c..9da913d 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -10,4 +10,14 @@
true
+
+
+ 0.1.0
+ dev
+
+
diff --git a/docs/CLI.md b/docs/CLI.md
index 666db8f..da5288f 100644
--- a/docs/CLI.md
+++ b/docs/CLI.md
@@ -234,5 +234,5 @@ done
```console
$ gclo --version
-0.1.0
+0.1.0-beta.6 (9ac6c2bde)
```
diff --git a/gclo.Cli/Program.cs b/gclo.Cli/Program.cs
index fe55bc0..9a8c6e9 100644
--- a/gclo.Cli/Program.cs
+++ b/gclo.Cli/Program.cs
@@ -1,4 +1,4 @@
-using System.Reflection;
+using gclo.Engine;
namespace gclo.Cli;
@@ -102,16 +102,7 @@ internal static class Program
}
private static string Version()
- {
- Assembly assembly = typeof(Program).Assembly;
- string version =
- assembly.GetCustomAttribute()?.InformationalVersion
- ?? assembly.GetName().Version?.ToString()
- ?? "unknown";
-
- // Strip the '+' suffix (e.g. a SourceLink commit hash); it is
- // noise for a scripted version check.
- int plus = version.IndexOf('+');
- return plus >= 0 ? version[..plus] : version;
- }
+ // Shared with the GUI's About dialog: "0.1.0-beta.6 (9ac6c2b)" for
+ // releases, "0.1.0-dev ()" for local builds.
+ => BuildVersion.Describe(typeof(Program).Assembly);
}
diff --git a/gclo.Cli/gclo.Cli.csproj b/gclo.Cli/gclo.Cli.csproj
index 31b9fc5..245e19d 100644
--- a/gclo.Cli/gclo.Cli.csproj
+++ b/gclo.Cli/gclo.Cli.csproj
@@ -8,7 +8,6 @@
gclo
gclo.Cli
true
- 0.1.0
diff --git a/gclo.Engine.Tests/BuildVersionTests.cs b/gclo.Engine.Tests/BuildVersionTests.cs
new file mode 100644
index 0000000..c6a846b
--- /dev/null
+++ b/gclo.Engine.Tests/BuildVersionTests.cs
@@ -0,0 +1,30 @@
+using gclo.Engine;
+
+namespace gclo.Engine.Tests;
+
+/// Formatting of the version + git-hash build identity string.
+public sealed class BuildVersionTests
+{
+ [Theory]
+ [InlineData(null, "unknown")]
+ [InlineData("", "unknown")]
+ [InlineData(" ", "unknown")]
+ [InlineData("1.2.3", "1.2.3")]
+ [InlineData("0.1.0-dev", "0.1.0-dev")]
+ [InlineData("0.1.0-beta.6+9ac6c2b1234567890abcdef", "0.1.0-beta.6 (9ac6c2b12)")]
+ [InlineData("1.2.3+abc", "1.2.3 (abc)")]
+ [InlineData("1.2.3+9ac6c2b1234567890.dirty", "1.2.3 (9ac6c2b12)")]
+ [InlineData("1.2.3+", "1.2.3")]
+ public void Format_ProducesReadableIdentity(string? informational, string expected)
+ {
+ Assert.Equal(expected, BuildVersion.Format(informational));
+ }
+
+ [Fact]
+ public void Describe_UsesThisAssembly_AndNeverThrows()
+ {
+ string described = BuildVersion.Describe(typeof(BuildVersionTests).Assembly);
+
+ Assert.False(string.IsNullOrWhiteSpace(described));
+ }
+}
diff --git a/gclo.Engine/BuildVersion.cs b/gclo.Engine/BuildVersion.cs
new file mode 100644
index 0000000..71fb03f
--- /dev/null
+++ b/gclo.Engine/BuildVersion.cs
@@ -0,0 +1,49 @@
+using System.Reflection;
+
+namespace gclo.Engine;
+
+///
+/// Human-readable build identity shared by the app's About dialog and the CLI's
+/// version output: the semantic version plus the short git commit the build came
+/// from, e.g. "0.1.0-beta.6 (9ac6c2b)" for releases or "0.1.0-dev (3a87f9e)" for
+/// local builds.
+///
+public static class BuildVersion
+{
+ private const int ShortHashLength = 9;
+
+ /// Describes 's version and commit.
+ public static string Describe(Assembly assembly)
+ => Format(assembly.GetCustomAttribute()?.InformationalVersion
+ ?? assembly.GetName().Version?.ToString());
+
+ ///
+ /// Formats an informational version: the "+<metadata>" suffix that SourceLink
+ /// appends becomes a parenthesized short commit hash.
+ ///
+ public static string Format(string? informationalVersion)
+ {
+ if (string.IsNullOrWhiteSpace(informationalVersion))
+ {
+ return "unknown";
+ }
+
+ int plus = informationalVersion.IndexOf('+');
+ if (plus < 0)
+ {
+ return informationalVersion;
+ }
+
+ string version = informationalVersion[..plus];
+ string metadata = informationalVersion[(plus + 1)..];
+ // Metadata may carry more than the sha (dot-separated); the hash is first.
+ int dot = metadata.IndexOf('.');
+ string hash = dot >= 0 ? metadata[..dot] : metadata;
+ if (hash.Length > ShortHashLength)
+ {
+ hash = hash[..ShortHashLength];
+ }
+
+ return hash.Length == 0 ? version : $"{version} ({hash})";
+ }
+}
diff --git a/gclo/AboutDialog.xaml.cs b/gclo/AboutDialog.xaml.cs
index 08173c9..113c75c 100644
--- a/gclo/AboutDialog.xaml.cs
+++ b/gclo/AboutDialog.xaml.cs
@@ -18,29 +18,13 @@ namespace gclo
}
///
- /// Returns the app version formatted as "major.minor.build". Prefers the MSIX
- /// package version; Package.Current throws when the app runs unpackaged
- /// (no package identity), in which case the assembly version is used instead.
+ /// Returns the build identity synced to the release pipeline: the semantic
+ /// version CI injects for the tag (0.1.0-dev on local builds) plus the short
+ /// git commit, e.g. "0.1.0-beta.6 (9ac6c2b)". The MSIX package version is
+ /// deliberately not used — its numeric quad cannot carry prerelease tags or
+ /// commit hashes, so it cannot distinguish a local build from a release.
///
private static string GetAppVersion()
- {
- try
- {
- var packageVersion = Windows.ApplicationModel.Package.Current.Id.Version;
- return $"{packageVersion.Major}.{packageVersion.Minor}.{packageVersion.Build}";
- }
- catch (Exception)
- {
- Version? assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version;
- if (assemblyVersion is null)
- {
- return "1.0.0";
- }
-
- // Version.Build is -1 when the assembly version has fewer than three parts.
- int build = assemblyVersion.Build < 0 ? 0 : assemblyVersion.Build;
- return $"{assemblyVersion.Major}.{assemblyVersion.Minor}.{build}";
- }
- }
+ => gclo.Engine.BuildVersion.Describe(Assembly.GetExecutingAssembly());
}
}