|
8 | 8 | using System.Linq;
|
9 | 9 | using System.Runtime.InteropServices;
|
10 | 10 | using System.Text;
|
| 11 | +using Validation; |
11 | 12 |
|
12 | 13 | namespace Nerdbank.GitVersioning.ManagedGit
|
13 | 14 | {
|
@@ -222,9 +223,7 @@ public object GetHeadAsReferenceOrSha()
|
222 | 223 | /// </returns>
|
223 | 224 | public GitObjectId GetHeadCommitSha()
|
224 | 225 | {
|
225 |
| - var reference = this.GetHeadAsReferenceOrSha(); |
226 |
| - var objectId = this.ResolveReference(reference); |
227 |
| - return objectId; |
| 226 | + return this.Lookup("HEAD") ?? GitObjectId.Empty; |
228 | 227 | }
|
229 | 228 |
|
230 | 229 | /// <summary>
|
@@ -280,16 +279,25 @@ public GitCommit GetCommit(GitObjectId sha, bool readAuthor = false)
|
280 | 279 | /// <returns>The object ID referenced by <paramref name="objectish"/> if found; otherwise <see langword="null"/>.</returns>
|
281 | 280 | public GitObjectId? Lookup(string objectish)
|
282 | 281 | {
|
| 282 | + bool skipObjectIdLookup = false; |
| 283 | + |
283 | 284 | if (objectish == "HEAD")
|
284 | 285 | {
|
285 |
| - return this.GetHeadCommitSha(); |
| 286 | + var reference = this.GetHeadAsReferenceOrSha(); |
| 287 | + if (reference is GitObjectId headObjectId) |
| 288 | + { |
| 289 | + return headObjectId; |
| 290 | + } |
| 291 | + |
| 292 | + objectish = (string)reference; |
286 | 293 | }
|
287 | 294 |
|
288 | 295 | var possibleLooseFileMatches = new List<string>();
|
289 | 296 | if (objectish.StartsWith("refs/", StringComparison.Ordinal))
|
290 | 297 | {
|
291 | 298 | // Match on loose ref files by their canonical name.
|
292 |
| - possibleLooseFileMatches.Add(Path.Combine(this.GitDirectory, objectish)); |
| 299 | + possibleLooseFileMatches.Add(Path.Combine(this.CommonDirectory, objectish)); |
| 300 | + skipObjectIdLookup = true; |
293 | 301 | }
|
294 | 302 | else
|
295 | 303 | {
|
@@ -341,6 +349,11 @@ public GitCommit GetCommit(GitObjectId sha, bool readAuthor = false)
|
341 | 349 | }
|
342 | 350 | }
|
343 | 351 |
|
| 352 | + if (skipObjectIdLookup) |
| 353 | + { |
| 354 | + return null; |
| 355 | + } |
| 356 | + |
344 | 357 | if (objectish.Length == 40)
|
345 | 358 | {
|
346 | 359 | return GitObjectId.Parse(objectish);
|
@@ -372,25 +385,30 @@ public GitCommit GetCommit(GitObjectId sha, bool readAuthor = false)
|
372 | 385 | objectish += "0";
|
373 | 386 | }
|
374 | 387 |
|
375 |
| - var hex = ConvertHexStringToByteArray(objectish); |
376 |
| - |
377 |
| - foreach (var pack in this.packs.Value.Span) |
| 388 | + if (objectish.Length <= 40 && objectish.Length % 2 == 0) |
378 | 389 | {
|
379 |
| - var objectId = pack.Lookup(hex, endsWithHalfByte); |
380 |
| - |
381 |
| - // It's possible for the same object to be present in both the object database and the pack files, |
382 |
| - // or in multiple pack files. |
383 |
| - if (objectId != null && !possibleObjectIds.Contains(objectId.Value)) |
| 390 | + Span<byte> decodedHex = stackalloc byte[objectish.Length / 2]; |
| 391 | + if (TryConvertHexStringToByteArray(objectish, decodedHex)) |
384 | 392 | {
|
385 |
| - if (possibleObjectIds.Count > 0) |
386 |
| - { |
387 |
| - // If objectish already resolved to at least one object which is different from the current |
388 |
| - // object id, objectish is not well-defined; so stop resolving and return null instead. |
389 |
| - return null; |
390 |
| - } |
391 |
| - else |
| 393 | + foreach (var pack in this.packs.Value.Span) |
392 | 394 | {
|
393 |
| - possibleObjectIds.Add(objectId.Value); |
| 395 | + var objectId = pack.Lookup(decodedHex, endsWithHalfByte); |
| 396 | + |
| 397 | + // It's possible for the same object to be present in both the object database and the pack files, |
| 398 | + // or in multiple pack files. |
| 399 | + if (objectId != null && !possibleObjectIds.Contains(objectId.Value)) |
| 400 | + { |
| 401 | + if (possibleObjectIds.Count > 0) |
| 402 | + { |
| 403 | + // If objectish already resolved to at least one object which is different from the current |
| 404 | + // object id, objectish is not well-defined; so stop resolving and return null instead. |
| 405 | + return null; |
| 406 | + } |
| 407 | + else |
| 408 | + { |
| 409 | + possibleObjectIds.Add(objectId.Value); |
| 410 | + } |
| 411 | + } |
394 | 412 | }
|
395 | 413 | }
|
396 | 414 | }
|
@@ -610,33 +628,6 @@ private bool TryGetObjectByPath(GitObjectId sha, string objectType, [NotNullWhen
|
610 | 628 | return true;
|
611 | 629 | }
|
612 | 630 |
|
613 |
| - private GitObjectId ResolveReference(object reference) |
614 |
| - { |
615 |
| - if (reference is string) |
616 |
| - { |
617 |
| - if (!FileHelpers.TryOpen(Path.Combine(this.CommonDirectory, (string)reference), out FileStream? stream)) |
618 |
| - { |
619 |
| - return GitObjectId.Empty; |
620 |
| - } |
621 |
| - |
622 |
| - using (stream) |
623 |
| - { |
624 |
| - Span<byte> objectId = stackalloc byte[40]; |
625 |
| - stream!.Read(objectId); |
626 |
| - |
627 |
| - return GitObjectId.ParseHex(objectId); |
628 |
| - } |
629 |
| - } |
630 |
| - else if (reference is GitObjectId) |
631 |
| - { |
632 |
| - return (GitObjectId)reference; |
633 |
| - } |
634 |
| - else |
635 |
| - { |
636 |
| - throw new GitException(); |
637 |
| - } |
638 |
| - } |
639 |
| - |
640 | 631 | private ReadOnlyMemory<GitPack> LoadPacks()
|
641 | 632 | {
|
642 | 633 | var packDirectory = Path.Combine(this.ObjectDirectory, "pack/");
|
@@ -687,22 +678,34 @@ private static string TrimEndingDirectorySeparator(string path)
|
687 | 678 | #endif
|
688 | 679 | }
|
689 | 680 |
|
690 |
| - private static byte[] ConvertHexStringToByteArray(string hexString) |
| 681 | + private static bool TryConvertHexStringToByteArray(string hexString, Span<byte> data) |
691 | 682 | {
|
692 | 683 | // https://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array
|
693 | 684 | if (hexString.Length % 2 != 0)
|
694 | 685 | {
|
695 |
| - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The binary key cannot have an odd number of digits: {0}", hexString)); |
| 686 | + data = null; |
| 687 | + return false; |
696 | 688 | }
|
697 | 689 |
|
698 |
| - byte[] data = new byte[hexString.Length / 2]; |
| 690 | + Requires.Argument(data.Length == hexString.Length / 2, nameof(data), "Length must be exactly half that of " + nameof(hexString) + "."); |
699 | 691 | for (int index = 0; index < data.Length; index++)
|
700 | 692 | {
|
| 693 | +#if NETCOREAPP3_1_OR_GREATER |
| 694 | + ReadOnlySpan<char> byteValue = hexString.AsSpan(index * 2, 2); |
| 695 | + if (!byte.TryParse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out data[index])) |
| 696 | + { |
| 697 | + return false; |
| 698 | + } |
| 699 | +#else |
701 | 700 | string byteValue = hexString.Substring(index * 2, 2);
|
702 |
| - data[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); |
| 701 | + if (!byte.TryParse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out data[index])) |
| 702 | + { |
| 703 | + return false; |
| 704 | + } |
| 705 | +#endif |
703 | 706 | }
|
704 | 707 |
|
705 |
| - return data; |
| 708 | + return true; |
706 | 709 | }
|
707 | 710 |
|
708 | 711 | /// <summary>
|
|
0 commit comments