Skip to content

Commit 864c725

Browse files
committed
Introduced sha256 support for git-sizer
1 parent b84ee4d commit 864c725

File tree

9 files changed

+135
-29
lines changed

9 files changed

+135
-29
lines changed

git/git.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ type Repository struct {
2424
// gitBin is the path of the `git` executable that should be used
2525
// when running commands in this repository.
2626
gitBin string
27+
// hashAgo is repository hash algo
28+
hashAlgo HashAlgo
2729
}
2830

2931
// smartJoin returns `relPath` if it is an absolute path. If not, it
@@ -49,9 +51,18 @@ func NewRepositoryFromGitDir(gitDir string) (*Repository, error) {
4951
)
5052
}
5153

54+
hashAlgo := HashSHA1
55+
cmd := exec.Command(gitBin, "--git-dir", gitDir, "rev-parse", "--show-object-format")
56+
if out, err := cmd.Output(); err == nil {
57+
if string(bytes.TrimSpace(out)) == "sha256" {
58+
hashAlgo = HashSHA256
59+
}
60+
}
61+
5262
repo := Repository{
53-
gitDir: gitDir,
54-
gitBin: gitBin,
63+
gitDir: gitDir,
64+
gitBin: gitBin,
65+
hashAlgo: hashAlgo,
5566
}
5667

5768
full, err := repo.IsFull()
@@ -170,3 +181,15 @@ func (repo *Repository) GitPath(relPath string) (string, error) {
170181
// current directory, we can use it as-is:
171182
return string(bytes.TrimSpace(out)), nil
172183
}
184+
185+
func (repo *Repository) HashAlgo() HashAlgo {
186+
return repo.hashAlgo
187+
}
188+
189+
func (repo *Repository) HashSize() int {
190+
return repo.hashAlgo.HashSize()
191+
}
192+
193+
func (repo *Repository) NullOID() OID {
194+
return repo.hashAlgo.NullOID()
195+
}

git/obj_iter.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (repo *Repository) NewObjectIter(ctx context.Context) (*ObjectIter, error)
3030
errCh: make(chan error),
3131
headerCh: make(chan BatchHeader),
3232
}
33-
33+
hashHexSize := repo.HashSize() * 2
3434
iter.p.Add(
3535
// Read OIDs from `iter.oidCh` and write them to `git
3636
// rev-list`:
@@ -68,10 +68,10 @@ func (repo *Repository) NewObjectIter(ctx context.Context) (*ObjectIter, error)
6868
pipe.LinewiseFunction(
6969
"copy-oids",
7070
func(_ context.Context, _ pipe.Env, line []byte, stdout *bufio.Writer) error {
71-
if len(line) < 40 {
71+
if len(line) < hashHexSize {
7272
return fmt.Errorf("line too short: '%s'", line)
7373
}
74-
if _, err := stdout.Write(line[:40]); err != nil {
74+
if _, err := stdout.Write(line[:hashHexSize]); err != nil {
7575
return fmt.Errorf("writing OID to 'git cat-file': %w", err)
7676
}
7777
if err := stdout.WriteByte('\n'); err != nil {

git/obj_resolver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ func (repo *Repository) ResolveObject(name string) (OID, error) {
99
cmd := repo.GitCommand("rev-parse", "--verify", "--end-of-options", name)
1010
output, err := cmd.Output()
1111
if err != nil {
12-
return NullOID, fmt.Errorf("resolving object %q: %w", name, err)
12+
return repo.NullOID(), fmt.Errorf("resolving object %q: %w", name, err)
1313
}
1414
oidString := string(bytes.TrimSpace(output))
1515
oid, err := NewOID(oidString)
1616
if err != nil {
17-
return NullOID, fmt.Errorf("parsing output %q from 'rev-parse': %w", oidString, err)
17+
return repo.NullOID(), fmt.Errorf("parsing output %q from 'rev-parse': %w", oidString, err)
1818
}
1919
return oid, nil
2020
}

git/oid.go

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,75 @@
11
package git
22

33
import (
4+
"bytes"
5+
"crypto/sha1"
6+
"crypto/sha256"
47
"encoding/hex"
58
"errors"
69
)
710

11+
const (
12+
HashSizeSHA256 = sha256.Size
13+
HashSizeSHA1 = sha1.Size
14+
HashSizeMax = HashSizeSHA256
15+
)
16+
17+
type HashAlgo int
18+
19+
const (
20+
HashUnknown HashAlgo = iota
21+
HashSHA1
22+
HashSHA256
23+
)
24+
825
// OID represents the SHA-1 object ID of a Git object, in binary
926
// format.
1027
type OID struct {
11-
v [20]byte
28+
v [HashSizeMax]byte
29+
hashSize int
1230
}
1331

14-
// NullOID is the null object ID; i.e., all zeros.
15-
var NullOID OID
32+
func (h HashAlgo) NullOID() OID {
33+
switch h {
34+
case HashSHA1:
35+
return OID{hashSize: HashSizeSHA1}
36+
case HashSHA256:
37+
return OID{hashSize: HashSizeSHA256}
38+
}
39+
return OID{}
40+
}
41+
42+
func (h HashAlgo) HashSize() int {
43+
switch h {
44+
case HashSHA1:
45+
return HashSizeSHA1
46+
case HashSHA256:
47+
return HashSizeSHA256
48+
}
49+
return 0
50+
}
51+
52+
// defaultNullOID is the null object ID; i.e., all zeros.
53+
var defaultNullOID OID
54+
55+
func IsNullOID(o OID) bool {
56+
return bytes.Equal(o.v[:], defaultNullOID.v[:])
57+
}
1658

1759
// OIDFromBytes converts a byte slice containing an object ID in
1860
// binary format into an `OID`.
1961
func OIDFromBytes(oidBytes []byte) (OID, error) {
2062
var oid OID
21-
if len(oidBytes) != len(oid.v) {
63+
oidSize := len(oidBytes)
64+
if oidSize != HashSizeSHA1 && oidSize != HashSizeSHA256 {
2265
return OID{}, errors.New("bytes oid has the wrong length")
2366
}
24-
copy(oid.v[0:20], oidBytes)
67+
oid.hashSize = oidSize
68+
copy(oid.v[0:oidSize], oidBytes)
2569
return oid, nil
2670
}
2771

28-
// NewOID converts an object ID in hex format (i.e., `[0-9a-f]{40}`)
29-
// into an `OID`.
72+
// NewOID converts an object ID in hex format (i.e., `[0-9a-f]{40,64}`) into an `OID`.
3073
func NewOID(s string) (OID, error) {
3174
oidBytes, err := hex.DecodeString(s)
3275
if err != nil {
@@ -37,18 +80,18 @@ func NewOID(s string) (OID, error) {
3780

3881
// String formats `oid` as a string in hex format.
3982
func (oid OID) String() string {
40-
return hex.EncodeToString(oid.v[:])
83+
return hex.EncodeToString(oid.v[:oid.hashSize])
4184
}
4285

4386
// Bytes returns a byte slice view of `oid`, in binary format.
4487
func (oid OID) Bytes() []byte {
45-
return oid.v[:]
88+
return oid.v[:oid.hashSize]
4689
}
4790

4891
// MarshalJSON expresses `oid` as a JSON string with its enclosing
4992
// quotation marks.
5093
func (oid OID) MarshalJSON() ([]byte, error) {
51-
src := oid.v[:]
94+
src := oid.v[:oid.hashSize]
5295
dst := make([]byte, hex.EncodedLen(len(src))+2)
5396
dst[0] = '"'
5497
dst[len(dst)-1] = '"'

git/tree.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import (
1010

1111
// Tree represents a Git tree object.
1212
type Tree struct {
13-
data string
13+
data string
14+
hashSize int
1415
}
1516

1617
// ParseTree parses the tree object whose contents are contained in
1718
// `data`. `oid` is currently unused.
1819
func ParseTree(oid OID, data []byte) (*Tree, error) {
19-
return &Tree{string(data)}, nil
20+
return &Tree{string(data), oid.hashSize}, nil
2021
}
2122

2223
// Size returns the size of the tree object.
@@ -36,13 +37,15 @@ type TreeEntry struct {
3637
// TreeIter is an iterator over the entries in a Git tree object.
3738
type TreeIter struct {
3839
// The as-yet-unread part of the tree's data.
39-
data string
40+
data string
41+
hashSize int
4042
}
4143

4244
// Iter returns an iterator over the entries in `tree`.
4345
func (tree *Tree) Iter() *TreeIter {
4446
return &TreeIter{
45-
data: tree.data,
47+
data: tree.data,
48+
hashSize: tree.hashSize,
4649
}
4750
}
4851

@@ -74,12 +77,12 @@ func (iter *TreeIter) NextEntry() (TreeEntry, bool, error) {
7477
entry.Name = iter.data[:nulAt]
7578

7679
iter.data = iter.data[nulAt+1:]
77-
if len(iter.data) < 20 {
80+
if len(iter.data) < iter.hashSize {
7881
return TreeEntry{}, false, errors.New("tree entry ends unexpectedly")
7982
}
80-
81-
copy(entry.OID.v[0:20], iter.data[0:20])
82-
iter.data = iter.data[20:]
83+
entry.OID.hashSize = iter.hashSize
84+
copy(entry.OID.v[0:iter.hashSize], iter.data[0:iter.hashSize])
85+
iter.data = iter.data[iter.hashSize:]
8386

8487
return entry, true, nil
8588
}

git_sizer_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,3 +849,40 @@ func TestSubmodule(t *testing.T) {
849849
assert.Equal(t, counts.Count32(2), h.UniqueBlobCount, "unique blob count")
850850
assert.Equal(t, counts.Count32(3), h.MaxExpandedBlobCount, "max expanded blob count")
851851
}
852+
853+
func TestSHA256(t *testing.T) {
854+
t.Parallel()
855+
856+
ctx := context.Background()
857+
858+
t.Helper()
859+
860+
path, err := os.MkdirTemp("", "sha256")
861+
require.NoError(t, err)
862+
863+
testRepo := testutils.TestRepo{Path: path}
864+
defer testRepo.Remove(t)
865+
866+
// Don't use `GitCommand()` because the directory might not
867+
// exist yet:
868+
cmd := exec.Command("git", "init", "--object-format", "sha256", testRepo.Path)
869+
cmd.Env = testutils.CleanGitEnv()
870+
err = cmd.Run()
871+
require.NoError(t, err)
872+
873+
timestamp := time.Unix(1112911993, 0)
874+
875+
testRepo.AddFile(t, "hello.txt", "Hello, world!\n")
876+
cmd = testRepo.GitCommand(t, "commit", "-m", "initial")
877+
testutils.AddAuthorInfo(cmd, &timestamp)
878+
require.NoError(t, cmd.Run(), "creating initial commit")
879+
880+
cmd = testRepo.GitCommand(t, "commit", "-m", "initial", "--allow-empty")
881+
testutils.AddAuthorInfo(cmd, &timestamp)
882+
require.NoError(t, cmd.Run(), "creating commit")
883+
884+
repo := testRepo.Repository(t)
885+
886+
_, err = sizes.CollectReferences(ctx, repo, refGrouper{})
887+
require.NoError(t, err)
888+
}

internal/testutils/repoutils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func (repo *TestRepo) UpdateRef(t *testing.T, refname string, oid git.OID) {
165165

166166
var cmd *exec.Cmd
167167

168-
if oid == git.NullOID {
168+
if git.IsNullOID(oid) {
169169
cmd = repo.GitCommand(t, "update-ref", "-d", refname)
170170
} else {
171171
cmd = repo.GitCommand(t, "update-ref", refname, oid.String())

sizes/graph.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func ScanRepositoryUsingGraph(
134134
case "tree":
135135
trees = append(trees, ObjectHeader{obj.OID, obj.ObjectSize})
136136
case "commit":
137-
commits = append(commits, CommitHeader{ObjectHeader{obj.OID, obj.ObjectSize}, git.NullOID})
137+
commits = append(commits, CommitHeader{ObjectHeader{obj.OID, obj.ObjectSize}, repo.NullOID()})
138138
case "tag":
139139
tags = append(tags, ObjectHeader{obj.OID, obj.ObjectSize})
140140
default:

sizes/output.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func (i *item) Emit(t *table) {
155155
}
156156

157157
func (i *item) Footnote(nameStyle NameStyle) string {
158-
if i.path == nil || i.path.OID == git.NullOID {
158+
if i.path == nil || git.IsNullOID(i.path.OID) {
159159
return ""
160160
}
161161
switch nameStyle {
@@ -214,7 +214,7 @@ func (i *item) MarshalJSON() ([]byte, error) {
214214
LevelOfConcern: float64(value) / i.scale,
215215
}
216216

217-
if i.path != nil && i.path.OID != git.NullOID {
217+
if i.path != nil && !git.IsNullOID(i.path.OID) {
218218
stat.ObjectName = i.path.OID.String()
219219
stat.ObjectDescription = i.path.Path()
220220
}

0 commit comments

Comments
 (0)