Skip to content

Commit 22bf587

Browse files
committed
Introduce repo.Describe()
1 parent 64cf16f commit 22bf587

13 files changed

+332
-0
lines changed

LibGit2Sharp.Tests/DescribeFixture.cs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Linq;
2+
using LibGit2Sharp.Tests.TestHelpers;
3+
using Xunit;
4+
5+
namespace LibGit2Sharp.Tests
6+
{
7+
public class DescribeFixture : BaseFixture
8+
{
9+
[Fact]
10+
public void CanDescribeACommit()
11+
{
12+
string path = SandboxBareTestRepo();
13+
using (var repo = new Repository(path))
14+
{
15+
// No annotated tags can be used to describe "master"
16+
var masterTip = repo.Branches["master"].Tip;
17+
Assert.Throws<NotFoundException>(() => repo.Describe(masterTip));
18+
Assert.Equal("4c062a6", repo.Describe(masterTip,
19+
new DescribeOptions { UseCommitIdAsFallback = true }));
20+
Assert.Equal("4c06", repo.Describe(masterTip,
21+
new DescribeOptions { UseCommitIdAsFallback = true, MinimumCommitIdAbbreviatedSize = 2 }));
22+
23+
// No lightweight tags can either be used to describe "master"
24+
Assert.Throws<NotFoundException>(() => repo.Describe(masterTip,
25+
new DescribeOptions{ Strategy = DescribeStrategy.Tags }));
26+
27+
repo.ApplyTag("myTag", "5b5b025afb0b4c913b4c338a42934a3863bf3644");
28+
Assert.Equal("myTag-5-g4c062a6", repo.Describe(masterTip,
29+
new DescribeOptions { Strategy = DescribeStrategy.Tags }));
30+
Assert.Equal("myTag-5-g4c062a636", repo.Describe(masterTip,
31+
new DescribeOptions { Strategy = DescribeStrategy.Tags, MinimumCommitIdAbbreviatedSize = 9 }));
32+
Assert.Equal("myTag-4-gbe3563a", repo.Describe(masterTip.Parents.Single(),
33+
new DescribeOptions { Strategy = DescribeStrategy.Tags }));
34+
35+
Assert.Equal("heads/master", repo.Describe(masterTip,
36+
new DescribeOptions { Strategy = DescribeStrategy.All }));
37+
Assert.Equal("heads/packed-test-3-gbe3563a", repo.Describe(masterTip.Parents.Single(),
38+
new DescribeOptions { Strategy = DescribeStrategy.All }));
39+
40+
// "test" branch points to an annotated tag (also named "test")
41+
// Let's rename the branch to ease the understanding of what we
42+
// are exercising.
43+
44+
repo.Branches.Rename(repo.Branches["test"], "ForLackOfABetterName");
45+
46+
var anotherTip = repo.Branches["ForLackOfABetterName"].Tip;
47+
Assert.Equal("test", repo.Describe(anotherTip));
48+
Assert.Equal("test-0-g7b43849", repo.Describe(anotherTip,
49+
new DescribeOptions{ AlwaysRenderLongFormat = true }));
50+
}
51+
}
52+
}
53+
}

LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
<Compile Include="ArchiveTarFixture.cs" />
5454
<Compile Include="CheckoutFixture.cs" />
5555
<Compile Include="CherryPickFixture.cs" />
56+
<Compile Include="DescribeFixture.cs" />
5657
<Compile Include="GlobalSettingsFixture.cs" />
5758
<Compile Include="PatchStatsFixture.cs" />
5859
<Compile Include="RefSpecFixture.cs" />

LibGit2Sharp/Core/Ensure.cs

+15
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,21 @@ public static void ArgumentConformsTo<T>(T argumentValue, Func<T, bool> checker,
198198
throw new ArgumentException(argumentName);
199199
}
200200

201+
/// <summary>
202+
/// Checks an argument is a positive integer.
203+
/// </summary>
204+
/// <param name="argumentValue">The argument value to check.</param>
205+
/// <param name="argumentName">The name of the argument.</param>
206+
public static void ArgumentPositiveInt32(long argumentValue, string argumentName)
207+
{
208+
if (argumentValue >= 0 && argumentValue <= uint.MaxValue)
209+
{
210+
return;
211+
}
212+
213+
throw new ArgumentException(argumentName);
214+
}
215+
201216
/// <summary>
202217
/// Check that the result of a C call that returns a non-null GitObject
203218
/// using the default exception builder.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core
5+
{
6+
[StructLayout(LayoutKind.Sequential)]
7+
internal struct GitDescribeFormatOptions
8+
{
9+
public uint Version;
10+
public uint MinAbbreviatedSize;
11+
public bool AlwaysUseLongFormat;
12+
public IntPtr DirtySuffix;
13+
}
14+
15+
[StructLayout(LayoutKind.Sequential)]
16+
internal struct GitDescribeOptions
17+
{
18+
public uint Version;
19+
public uint MaxCandidatesTags;
20+
public DescribeStrategy DescribeStrategy;
21+
public IntPtr Pattern;
22+
public bool OnlyFollowFirstParent;
23+
public bool ShowCommitOidAsFallback;
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace LibGit2Sharp.Core.Handles
2+
{
3+
internal class DescribeResultSafeHandle : SafeHandleBase
4+
{
5+
protected override bool ReleaseHandleImpl()
6+
{
7+
Proxy.git_describe_free(handle);
8+
return true;
9+
}
10+
}
11+
}

LibGit2Sharp/Core/NativeMethods.cs

+15
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,21 @@ internal static extern int git_cred_userpass_plaintext_new(
422422
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string username,
423423
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string password);
424424

425+
[DllImport(libgit2)]
426+
internal static extern int git_describe_commit(
427+
out DescribeResultSafeHandle describe,
428+
GitObjectSafeHandle committish,
429+
ref GitDescribeOptions options);
430+
431+
[DllImport(libgit2)]
432+
internal static extern int git_describe_format(
433+
GitBuf buf,
434+
DescribeResultSafeHandle describe,
435+
ref GitDescribeFormatOptions options);
436+
437+
[DllImport(libgit2)]
438+
internal static extern void git_describe_result_free(IntPtr describe);
439+
425440
[DllImport(libgit2)]
426441
internal static extern void git_diff_free(IntPtr diff);
427442

LibGit2Sharp/Core/Proxy.cs

+62
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,68 @@ public static ConfigurationSafeHandle git_config_snapshot(ConfigurationSafeHandl
608608

609609
#endregion
610610

611+
#region git_describe_
612+
613+
public static string git_describe_commit(
614+
RepositorySafeHandle repo,
615+
ObjectId committishId,
616+
DescribeOptions options)
617+
{
618+
Ensure.ArgumentPositiveInt32(options.MinimumCommitIdAbbreviatedSize,
619+
"options.MinimumCommitIdAbbreviatedSize");
620+
621+
using (ThreadAffinity())
622+
using (var osw = new ObjectSafeWrapper(committishId, repo))
623+
{
624+
GitDescribeOptions opts = new GitDescribeOptions
625+
{
626+
Version = 1,
627+
DescribeStrategy = options.Strategy,
628+
MaxCandidatesTags = 10,
629+
OnlyFollowFirstParent = false,
630+
ShowCommitOidAsFallback = options.UseCommitIdAsFallback,
631+
};
632+
633+
DescribeResultSafeHandle describeHandle = null;
634+
635+
try
636+
{
637+
int res = NativeMethods.git_describe_commit(out describeHandle, osw.ObjectPtr, ref opts);
638+
Ensure.ZeroResult(res);
639+
640+
using (var buf = new GitBuf())
641+
{
642+
GitDescribeFormatOptions formatOptions = new GitDescribeFormatOptions
643+
{
644+
Version = 1,
645+
MinAbbreviatedSize = (uint)options.MinimumCommitIdAbbreviatedSize,
646+
AlwaysUseLongFormat = options.AlwaysRenderLongFormat,
647+
};
648+
649+
res = NativeMethods.git_describe_format(buf, describeHandle, ref formatOptions);
650+
Ensure.ZeroResult(res);
651+
652+
describeHandle.Dispose();
653+
return LaxUtf8Marshaler.FromNative(buf.ptr);
654+
}
655+
}
656+
finally
657+
{
658+
if (describeHandle != null)
659+
{
660+
describeHandle.Dispose();
661+
}
662+
}
663+
}
664+
}
665+
666+
public static void git_describe_free(IntPtr iter)
667+
{
668+
NativeMethods.git_describe_result_free(iter);
669+
}
670+
671+
#endregion
672+
611673
#region git_diff_
612674

613675
public static void git_diff_blobs(

LibGit2Sharp/DescribeOptions.cs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
namespace LibGit2Sharp
2+
{
3+
/// <summary>
4+
/// Options to define describe behaviour
5+
/// </summary>
6+
public sealed class DescribeOptions
7+
{
8+
/// <summary>
9+
/// Initializes a new instance of the <see cref="DescribeOptions"/> class.
10+
/// <para>
11+
/// By default:
12+
/// - Only annotated tags will be considered as reference points
13+
/// - The commit id won't be used as a fallback strategy
14+
/// - Only the 10 most recent tags will be considered as candidates to describe the commit
15+
/// - All ancestor lines will be followed upon seeing a merge commit
16+
/// - 7 hexacidemal digits will be used as a minimum commid abbreviated size
17+
/// - Long format will only be used when no direct match has been found
18+
/// </para>
19+
/// </summary>
20+
public DescribeOptions()
21+
{
22+
Strategy = DescribeStrategy.Default;
23+
MinimumCommitIdAbbreviatedSize = 7;
24+
}
25+
26+
/// <summary>
27+
/// The kind of references that will be eligible as reference points.
28+
/// </summary>
29+
public DescribeStrategy Strategy { get; set; }
30+
31+
/// <summary>
32+
/// Rather than throwing, should <see cref="IRepository.Describe"/> return
33+
/// the abbreviated commit id when the selected <see cref="Strategy"/>
34+
/// didn't identify a proper reference to describe the commit.
35+
/// </summary>
36+
public bool UseCommitIdAsFallback { get; set; }
37+
38+
/// <summary>
39+
/// Number of minimum hexadecimal digits used to render a uniquely
40+
/// abbreviated commit id.
41+
/// </summary>
42+
public int MinimumCommitIdAbbreviatedSize { get; set; }
43+
44+
/// <summary>
45+
/// Always output the long format (the tag, the number of commits
46+
/// and the abbreviated commit name) even when a direct match has been
47+
/// found.
48+
/// <para>
49+
/// This is useful when one wants to see parts of the commit object
50+
/// name in "describe" output, even when the commit in question happens
51+
/// to be a tagged version. Instead of just emitting the tag name, it
52+
/// will describe such a commit as v1.2-0-gdeadbee (0th commit since
53+
/// tag v1.2 that points at object deadbee...).
54+
/// </para>
55+
/// </summary>
56+
public bool AlwaysRenderLongFormat { get; set; }
57+
}
58+
}

LibGit2Sharp/DescribeStrategy.cs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace LibGit2Sharp
2+
{
3+
/// <summary>
4+
/// Specify the kind of committish which will be considered
5+
/// when trying to identify the closest reference to the described commit.
6+
/// </summary>
7+
public enum DescribeStrategy
8+
{
9+
/// <summary>
10+
/// Only consider annotated tags.
11+
/// </summary>
12+
Default = 0,
13+
14+
/// <summary>
15+
/// Consider both annotated and lightweight tags.
16+
/// <para>
17+
/// This will match every reference under the <code>refs/tags/</code> namespace.
18+
/// </para>
19+
/// </summary>
20+
Tags,
21+
22+
/// <summary>
23+
/// Consider annotated and lightweight tags, local and remote tracking branches.
24+
/// <para>
25+
/// This will match every reference under the <code>refs/</code> namespace.
26+
/// </para>
27+
/// </summary>
28+
All,
29+
}
30+
}

LibGit2Sharp/IRepository.cs

+18
Original file line numberDiff line numberDiff line change
@@ -365,5 +365,23 @@ public interface IRepository : IDisposable
365365
/// <param name="options">If set, the options that control the status investigation.</param>
366366
/// <returns>A <see cref="RepositoryStatus"/> holding the state of all the files.</returns>
367367
RepositoryStatus RetrieveStatus(StatusOptions options);
368+
369+
/// <summary>
370+
/// Finds the most recent annotated tag that is reachable from a commit.
371+
/// <para>
372+
/// If the tag points to the commit, then only the tag is shown. Otherwise,
373+
/// it suffixes the tag name with the number of additional commits on top
374+
/// of the tagged object and the abbreviated object name of the most recent commit.
375+
/// </para>
376+
/// <para>
377+
/// Optionally, the <paramref name="options"/> parameter allow to tweak the
378+
/// search strategy (considering lightweith tags, or even branches as reference points)
379+
/// and the formatting of the returned identifier.
380+
/// </para>
381+
/// </summary>
382+
/// <param name="commit">The commit to be described.</param>
383+
/// <param name="options">Determines how the commit will be described.</param>
384+
/// <returns>A descriptive identifier for the commit based on the nearest annotated tag.</returns>
385+
string Describe(Commit commit, DescribeOptions options);
368386
}
369387
}

LibGit2Sharp/LibGit2Sharp.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,12 @@
6868
<Compile Include="CommitOptions.cs" />
6969
<Compile Include="CommitSortStrategies.cs" />
7070
<Compile Include="CompareOptions.cs" />
71+
<Compile Include="DescribeOptions.cs" />
72+
<Compile Include="DescribeStrategy.cs" />
73+
<Compile Include="Core\GitDescribeFormatOptions.cs" />
7174
<Compile Include="Core\GitIndexReucEntry.cs" />
7275
<Compile Include="Core\GitSubmoduleOptions.cs" />
76+
<Compile Include="Core\Handles\DescribeResultSafeHandle.cs" />
7377
<Compile Include="Core\Handles\IndexNameEntrySafeHandle.cs" />
7478
<Compile Include="Core\Handles\IndexReucEntrySafeHandle.cs" />
7579
<Compile Include="EntryExistsException.cs" />

LibGit2Sharp/Repository.cs

+24
Original file line numberDiff line numberDiff line change
@@ -1891,6 +1891,30 @@ private IEnumerable<string> RemoveStagedItems(IEnumerable<string> paths, bool re
18911891
return removed;
18921892
}
18931893

1894+
/// <summary>
1895+
/// Finds the most recent annotated tag that is reachable from a commit.
1896+
/// <para>
1897+
/// If the tag points to the commit, then only the tag is shown. Otherwise,
1898+
/// it suffixes the tag name with the number of additional commits on top
1899+
/// of the tagged object and the abbreviated object name of the most recent commit.
1900+
/// </para>
1901+
/// <para>
1902+
/// Optionally, the <paramref name="options"/> parameter allow to tweak the
1903+
/// search strategy (considering lightweith tags, or even branches as reference points)
1904+
/// and the formatting of the returned identifier.
1905+
/// </para>
1906+
/// </summary>
1907+
/// <param name="commit">The commit to be described.</param>
1908+
/// <param name="options">Determines how the commit will be described.</param>
1909+
/// <returns>A descriptive identifier for the commit based on the nearest annotated tag.</returns>
1910+
public string Describe(Commit commit, DescribeOptions options)
1911+
{
1912+
Ensure.ArgumentNotNull(commit, "commit");
1913+
Ensure.ArgumentNotNull(options, "options");
1914+
1915+
return Proxy.git_describe_commit(handle, commit.Id, options);
1916+
}
1917+
18941918
private string DebuggerDisplay
18951919
{
18961920
get

LibGit2Sharp/RepositoryExtensions.cs

+16
Original file line numberDiff line numberDiff line change
@@ -738,5 +738,21 @@ public static RepositoryStatus RetrieveStatus(this IRepository repository)
738738
Proxy.git_index_read(repository.Index.Handle);
739739
return new RepositoryStatus((Repository)repository, null);
740740
}
741+
742+
/// <summary>
743+
/// Finds the most recent annotated tag that is reachable from a commit.
744+
/// <para>
745+
/// If the tag points to the commit, then only the tag is shown. Otherwise,
746+
/// it suffixes the tag name with the number of additional commits on top
747+
/// of the tagged object and the abbreviated object name of the most recent commit.
748+
/// </para>
749+
/// </summary>
750+
/// <param name="repository">The <see cref="IRepository"/> being worked with.</param>
751+
/// <param name="commit">The commit to be described.</param>
752+
/// <returns>A descriptive identifier for the commit based on the nearest annotated tag.</returns>
753+
public static string Describe(this IRepository repository, Commit commit)
754+
{
755+
return repository.Describe(commit, new DescribeOptions());
756+
}
741757
}
742758
}

0 commit comments

Comments
 (0)