@@ -3,6 +3,7 @@ package git
3
3
import (
4
4
"bytes"
5
5
"context"
6
+ "encoding/hex"
6
7
"errors"
7
8
"fmt"
8
9
"io"
@@ -1426,7 +1427,7 @@ func (r *Repository) Worktree() (*Worktree, error) {
1426
1427
// resolve to a commit hash, not a tree or annotated tag.
1427
1428
//
1428
1429
// Implemented resolvers : HEAD, branch, tag, heads/branch, refs/heads/branch,
1429
- // refs/tags/tag, refs/remotes/origin/branch, refs/remotes/origin/HEAD, tilde and caret (HEAD~1, master~^, tag~2, ref/heads/master~1, ...), selection by text (HEAD^{/fix nasty bug})
1430
+ // refs/tags/tag, refs/remotes/origin/branch, refs/remotes/origin/HEAD, tilde and caret (HEAD~1, master~^, tag~2, ref/heads/master~1, ...), selection by text (HEAD^{/fix nasty bug}), hash (prefix and full)
1430
1431
func (r * Repository ) ResolveRevision (rev plumbing.Revision ) (* plumbing.Hash , error ) {
1431
1432
p := revision .NewParserFromString (string (rev ))
1432
1433
@@ -1445,11 +1446,7 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
1445
1446
1446
1447
var tryHashes []plumbing.Hash
1447
1448
1448
- maybeHash := plumbing .NewHash (string (revisionRef ))
1449
-
1450
- if ! maybeHash .IsZero () {
1451
- tryHashes = append (tryHashes , maybeHash )
1452
- }
1449
+ tryHashes = append (tryHashes , r .resolveHashPrefix (string (revisionRef ))... )
1453
1450
1454
1451
for _ , rule := range append ([]string {"%s" }, plumbing .RefRevParseRules ... ) {
1455
1452
ref , err := storer .ResolveReference (r .Storer , plumbing .ReferenceName (fmt .Sprintf (rule , revisionRef )))
@@ -1567,6 +1564,49 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err
1567
1564
return & commit .Hash , nil
1568
1565
}
1569
1566
1567
+ // resolveHashPrefix returns a list of potential hashes that the given string
1568
+ // is a prefix of. It quietly swallows errors, returning nil.
1569
+ func (r * Repository ) resolveHashPrefix (hashStr string ) []plumbing.Hash {
1570
+ // Handle complete and partial hashes.
1571
+ // plumbing.NewHash forces args into a full 20 byte hash, which isn't suitable
1572
+ // for partial hashes since they will become zero-filled.
1573
+
1574
+ if hashStr == "" {
1575
+ return nil
1576
+ }
1577
+ if len (hashStr ) == len (plumbing .ZeroHash )* 2 {
1578
+ // Only a full hash is possible.
1579
+ hexb , err := hex .DecodeString (hashStr )
1580
+ if err != nil {
1581
+ return nil
1582
+ }
1583
+ var h plumbing.Hash
1584
+ copy (h [:], hexb )
1585
+ return []plumbing.Hash {h }
1586
+ }
1587
+
1588
+ // Partial hash.
1589
+ // hex.DecodeString only decodes to complete bytes, so only works with pairs of hex digits.
1590
+ evenHex := hashStr [:len (hashStr )&^1 ]
1591
+ hexb , err := hex .DecodeString (evenHex )
1592
+ if err != nil {
1593
+ return nil
1594
+ }
1595
+ candidates := expandPartialHash (r .Storer , hexb )
1596
+ if len (evenHex ) == len (hashStr ) {
1597
+ // The prefix was an exact number of bytes.
1598
+ return candidates
1599
+ }
1600
+ // Do another prefix check to ensure the dangling nybble is correct.
1601
+ var hashes []plumbing.Hash
1602
+ for _ , h := range candidates {
1603
+ if strings .HasPrefix (h .String (), hashStr ) {
1604
+ hashes = append (hashes , h )
1605
+ }
1606
+ }
1607
+ return hashes
1608
+ }
1609
+
1570
1610
type RepackConfig struct {
1571
1611
// UseRefDeltas configures whether packfile encoder will use reference deltas.
1572
1612
// By default OFSDeltaObject is used.
@@ -1659,3 +1699,31 @@ func (r *Repository) createNewObjectPack(cfg *RepackConfig) (h plumbing.Hash, er
1659
1699
1660
1700
return h , err
1661
1701
}
1702
+
1703
+ func expandPartialHash (st storer.EncodedObjectStorer , prefix []byte ) (hashes []plumbing.Hash ) {
1704
+ // The fast version is implemented by storage/filesystem.ObjectStorage.
1705
+ type fastIter interface {
1706
+ HashesWithPrefix (prefix []byte ) ([]plumbing.Hash , error )
1707
+ }
1708
+ if fi , ok := st .(fastIter ); ok {
1709
+ h , err := fi .HashesWithPrefix (prefix )
1710
+ if err != nil {
1711
+ return nil
1712
+ }
1713
+ return h
1714
+ }
1715
+
1716
+ // Slow path.
1717
+ iter , err := st .IterEncodedObjects (plumbing .AnyObject )
1718
+ if err != nil {
1719
+ return nil
1720
+ }
1721
+ iter .ForEach (func (obj plumbing.EncodedObject ) error {
1722
+ h := obj .Hash ()
1723
+ if bytes .HasPrefix (h [:], prefix ) {
1724
+ hashes = append (hashes , h )
1725
+ }
1726
+ return nil
1727
+ })
1728
+ return
1729
+ }
0 commit comments