Skip to content

Commit

Permalink
git: tidy code around digests
Browse files Browse the repository at this point in the history
Signed-off-by: Hidde Beydals <[email protected]>
  • Loading branch information
hiddeco committed Dec 13, 2022
1 parent 6e2b4a4 commit 340cf50
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 53 deletions.
36 changes: 32 additions & 4 deletions git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,36 @@ import (
"github.com/ProtonMail/go-crypto/openpgp"
)

const (
// HashTypeSHA1 is the SHA1 hash algorithm.
HashTypeSHA1 = "sha1"
// HashTypeUnknown is an unknown hash algorithm.
HashTypeUnknown = "<unknown>"
)

// Hash is the (non-truncated) SHA-1 or SHA-256 hash of a Git commit.
type Hash []byte

// Algorithm returns the algorithm of the hash based on its length.
// This is a heuristic, and may not be accurate for truncated user constructed
// hashes. The library itself does not produce truncated hashes.
func (h Hash) Algorithm() string {
switch len(h) {
case 40:
return HashTypeSHA1
default:
return HashTypeUnknown
}
}

// Digest returns a digest of the commit, in the format of "<algorithm>:<hash>".
func (h Hash) Digest() string {
if len(h) == 0 {
return ""
}
return fmt.Sprintf("%s:%s", h.Algorithm(), h)
}

// String returns the Hash as a string.
func (h Hash) String() string {
return string(h)
Expand All @@ -43,7 +71,7 @@ type Signature struct {

// Commit contains all possible information about a Git commit.
type Commit struct {
// Hash is the SHA1 hash of the commit.
// Hash is the hash of the commit.
Hash Hash
// Reference is the original reference of the commit, for example:
// 'refs/tags/foo'.
Expand All @@ -57,7 +85,7 @@ type Commit struct {
Signature string
// Encoded is the encoded commit, without any signature.
Encoded []byte
// Message is the commit message, contains arbitrary text.
// Message is the commit message, containing arbitrary text.
Message string
}

Expand All @@ -67,9 +95,9 @@ type Commit struct {
// for a "tag-1" tag.
func (c *Commit) String() string {
if short := strings.SplitAfterN(c.Reference, "/", 3); len(short) == 3 {
return fmt.Sprintf("%s@sha1:%s", short[2], c.Hash)
return fmt.Sprintf("%s@%s", short[2], c.Hash.Digest())
}
return fmt.Sprintf("sha1:%s", c.Hash)
return c.Hash.Digest()
}

// Verify the Signature of the commit with the given key rings.
Expand Down
89 changes: 83 additions & 6 deletions git/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,83 @@ Bg2WzDuLKvQBi9tFSwnUbQoFFlOeiGW8G/bdkoJDWeS1oYgSD3nkmvXvrVESCrbT
`
)

func TestHash_Algorithm(t *testing.T) {
tests := []struct {
name string
hash Hash
want string
}{
{
name: "SHA-1",
hash: Hash("5394cb7f48332b2de7c17dd8b8384bbc84b7e738"),
want: HashTypeSHA1,
},
{
name: "SHA-256",
hash: Hash("6ee9a7ade2ca791bc1bf9d133ef6ddaa9097cf521e6a19be92dbcc3f2e82f6d8"),
want: HashTypeUnknown,
},
{
name: "MD5",
hash: Hash("dba535cd50b291777a055338572e4a4b"),
want: HashTypeUnknown,
},
{
name: "Empty",
hash: Hash(""),
want: HashTypeUnknown,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

g.Expect(tt.hash.Algorithm()).To(Equal(tt.want))
})
}
}

func TestHash_Digest(t *testing.T) {
tests := []struct {
name string
hash Hash
want string
}{
{
name: "With a SHA-1 hash",
hash: Hash("5394cb7f48332b2de7c17dd8b8384bbc84b7e738"),
want: "sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
},
{
name: "With an unknown (MD5) hash",
hash: Hash("dba535cd50b291777a055338572e4a4b"),
want: "<unknown>:dba535cd50b291777a055338572e4a4b",
},
{
name: "With an unknown (SHA-256) hash",
hash: Hash("6ee9a7ade2ca791bc1bf9d133ef6ddaa9097cf521e6a19be92dbcc3f2e82f6d8"),
want: "<unknown>:6ee9a7ade2ca791bc1bf9d133ef6ddaa9097cf521e6a19be92dbcc3f2e82f6d8",
},
{
name: "With a nil hash",
hash: nil,
want: "",
},
{
name: "With an empty hash",
hash: Hash(""),
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

g.Expect(tt.hash.Digest()).To(Equal(tt.want))
})
}
}

func TestCommit_String(t *testing.T) {
tests := []struct {
name string
Expand All @@ -128,25 +205,25 @@ func TestCommit_String(t *testing.T) {
{
name: "Reference and commit",
commit: &Commit{
Hash: []byte("commit"),
Hash: []byte("5394cb7f48332b2de7c17dd8b8384bbc84b7e738"),
Reference: "refs/heads/main",
},
want: "main@sha1:commit",
want: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
},
{
name: "Reference with slash and commit",
commit: &Commit{
Hash: []byte("commit"),
Hash: []byte("5394cb7f48332b2de7c17dd8b8384bbc84b7e738"),
Reference: "refs/heads/feature/branch",
},
want: "feature/branch@sha1:commit",
want: "feature/branch@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
},
{
name: "No name reference",
commit: &Commit{
Hash: []byte("commit"),
Hash: []byte("5394cb7f48332b2de7c17dd8b8384bbc84b7e738"),
},
want: "sha1:commit",
want: "sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738",
},
}
for _, tt := range tests {
Expand Down
26 changes: 3 additions & 23 deletions git/gogit/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,8 @@ func (g *Client) cloneBranch(ctx context.Context, url, branch string, opts repos
}

if head != "" && head == opts.LastObservedCommit {
// Construct a non-concrete commit with the existing information.
// Split the revision and take the last part as the hash.
// Example revision: main@sha1:43d7eb9c49cdd49b2494efd481aea1166fc22b67
var hash git.Hash
ss := strings.Split(head, "@")
if len(ss) > 1 {
hash = git.Hash(strings.TrimPrefix(ss[len(ss)-1], "sha1:"))
} else {
hash = git.Hash(strings.TrimPrefix(ss[0], "sha1:"))
}
c := &git.Commit{
Hash: hash,
Hash: git.ExtractHashFromRevision(head),
Reference: plumbing.NewBranchReferenceName(branch).String(),
}
return c, nil
Expand Down Expand Up @@ -146,18 +136,8 @@ func (g *Client) cloneTag(ctx context.Context, url, tag string, opts repository.
}

if head != "" && head == opts.LastObservedCommit {
// Construct a non-concrete commit with the existing information.
// Split the revision and take the last part as the hash.
// Example revision: 6.1.4@sha1:bf09377bfd5d3bcac1e895fa8ce52dc76695c060
var hash git.Hash
ss := strings.Split(head, "@")
if len(ss) > 1 {
hash = git.Hash(strings.TrimPrefix(ss[len(ss)-1], "sha1:"))
} else {
hash = git.Hash(strings.TrimPrefix(ss[0], "sha1:"))
}
c := &git.Commit{
Hash: hash,
Hash: git.ExtractHashFromRevision(head),
Reference: ref.String(),
}
return c, nil
Expand Down Expand Up @@ -406,7 +386,7 @@ func getRemoteHEAD(ctx context.Context, url string, ref plumbing.ReferenceName,
func filterRefs(refs []*plumbing.Reference, currentRef plumbing.ReferenceName) string {
for _, ref := range refs {
if ref.Name().String() == currentRef.String() {
return fmt.Sprintf("%s@sha1:%s", currentRef.Short(), ref.Hash().String())
return fmt.Sprintf("%s@%s:%s", currentRef.Short(), git.HashTypeSHA1, ref.Hash().String())
}
}
return ""
Expand Down
20 changes: 10 additions & 10 deletions git/gogit/clone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,15 @@ func TestClone_cloneBranch(t *testing.T) {
name: "skip clone if LastRevision hasn't changed",
branch: "master",
filesCreated: map[string]string{"branch": "init"},
lastRevision: fmt.Sprintf("master@sha1:%s", firstCommit.String()),
lastRevision: fmt.Sprintf("master@%s:%s", git.HashTypeSHA1, firstCommit.String()),
expectedCommit: firstCommit.String(),
expectedConcreteCommit: false,
},
{
name: "Other branch - revision has changed",
branch: "test",
filesCreated: map[string]string{"branch": "second"},
lastRevision: fmt.Sprintf("master@sha1:%s", firstCommit.String()),
lastRevision: fmt.Sprintf("master@%s:%s", git.HashTypeSHA1, firstCommit.String()),
expectedCommit: secondCommit.String(),
expectedConcreteCommit: true,
},
Expand Down Expand Up @@ -157,7 +157,7 @@ func TestClone_cloneBranch(t *testing.T) {
}

g.Expect(err).ToNot(HaveOccurred())
g.Expect(cc.String()).To(Equal(tt.branch + "@sha1:" + tt.expectedCommit))
g.Expect(cc.String()).To(Equal(tt.branch + "@" + git.HashTypeSHA1 + ":" + tt.expectedCommit))
g.Expect(git.IsConcreteCommit(*cc)).To(Equal(tt.expectedConcreteCommit))

if tt.expectedConcreteCommit {
Expand Down Expand Up @@ -258,7 +258,7 @@ func TestClone_cloneTag(t *testing.T) {
// If last revision is provided, configure it.
if tt.lastRevTag != "" {
lc := tagCommits[tt.lastRevTag]
opts.LastObservedCommit = fmt.Sprintf("%s@sha1:%s", tt.lastRevTag, lc)
opts.LastObservedCommit = fmt.Sprintf("%s@%s:%s", tt.lastRevTag, git.HashTypeSHA1, lc)
}

cc, err := ggc.Clone(context.TODO(), path, opts)
Expand All @@ -274,7 +274,7 @@ func TestClone_cloneTag(t *testing.T) {
g.Expect(git.IsConcreteCommit(*cc)).To(Equal(tt.expectConcreteCommit))
targetTagHash := tagCommits[tt.checkoutTag]
g.Expect(err).ToNot(HaveOccurred())
g.Expect(cc.String()).To(Equal(tt.checkoutTag + "@sha1:" + targetTagHash))
g.Expect(cc.String()).To(Equal(tt.checkoutTag + "@" + git.HashTypeSHA1 + ":" + targetTagHash))

// Check file content only when there's an actual checkout.
if tt.lastRevTag != tt.checkoutTag {
Expand Down Expand Up @@ -314,14 +314,14 @@ func TestClone_cloneCommit(t *testing.T) {
{
name: "Commit",
commit: firstCommit.String(),
expectCommit: "sha1:" + firstCommit.String(),
expectCommit: git.HashTypeSHA1 + ":" + firstCommit.String(),
expectFile: "init",
},
{
name: "Commit in specific branch",
commit: secondCommit.String(),
branch: "other-branch",
expectCommit: "other-branch@sha1:" + secondCommit.String(),
expectCommit: "other-branch@" + git.HashTypeSHA1 + ":" + secondCommit.String(),
expectFile: "second",
},
{
Expand Down Expand Up @@ -470,7 +470,7 @@ func TestClone_cloneSemVer(t *testing.T) {
}

g.Expect(err).ToNot(HaveOccurred())
g.Expect(cc.String()).To(Equal(tt.expectTag + "@sha1:" + refs[tt.expectTag]))
g.Expect(cc.String()).To(Equal(tt.expectTag + "@" + git.HashTypeSHA1 + ":" + refs[tt.expectTag]))
g.Expect(filepath.Join(tmpDir, "tag")).To(BeARegularFile())
g.Expect(os.ReadFile(filepath.Join(tmpDir, "tag"))).To(BeEquivalentTo(tt.expectTag))
})
Expand Down Expand Up @@ -970,7 +970,7 @@ func Test_getRemoteHEAD(t *testing.T) {
ref := plumbing.NewBranchReferenceName(git.DefaultBranch)
head, err := getRemoteHEAD(context.TODO(), path, ref, &git.AuthOptions{}, nil)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(head).To(Equal(fmt.Sprintf("%s@sha1:%s", git.DefaultBranch, cc)))
g.Expect(head).To(Equal(fmt.Sprintf("%s@%s:%s", git.DefaultBranch, git.HashTypeSHA1, cc)))

cc, err = commitFile(repo, "test", "testing current head tag", time.Now())
g.Expect(err).ToNot(HaveOccurred())
Expand All @@ -980,7 +980,7 @@ func Test_getRemoteHEAD(t *testing.T) {
ref = plumbing.NewTagReferenceName("v0.1.0")
head, err = getRemoteHEAD(context.TODO(), path, ref, &git.AuthOptions{}, nil)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(head).To(Equal(fmt.Sprintf("%s@sha1:%s", "v0.1.0", cc)))
g.Expect(head).To(Equal(fmt.Sprintf("%s@%s:%s", "v0.1.0", git.HashTypeSHA1, cc)))
}

func TestClone_CredentialsOverHttp(t *testing.T) {
Expand Down
6 changes: 3 additions & 3 deletions git/libgit2/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (l *Client) cloneBranch(ctx context.Context, url, branch string, opts repos
}
if len(heads) > 0 {
hash := heads[0].Id.String()
remoteHead := fmt.Sprintf("%s@sha1:%s", branch, hash)
remoteHead := fmt.Sprintf("%s@%s:%s", branch, git.HashTypeSHA1, hash)
if remoteHead == opts.LastObservedCommit {
// Construct a non-concrete commit with the existing information.
c := &git.Commit{
Expand Down Expand Up @@ -173,13 +173,13 @@ func (l *Client) cloneTag(ctx context.Context, url, tag string, opts repository.
}
if len(heads) > 0 {
hash := heads[0].Id.String()
remoteHEAD := fmt.Sprintf("%s@sha1:%s", tag, hash)
remoteHEAD := fmt.Sprintf("%s@%s:%s", tag, git.HashTypeSHA1, hash)
var same bool
if remoteHEAD == opts.LastObservedCommit {
same = true
} else if len(heads) > 1 {
hash = heads[1].Id.String()
remoteAnnotatedHEAD := fmt.Sprintf("%s@sha1:%s", tag, hash)
remoteAnnotatedHEAD := fmt.Sprintf("%s@%s:%s", tag, git.HashTypeSHA1, hash)
if remoteAnnotatedHEAD == opts.LastObservedCommit {
same = true
}
Expand Down
Loading

0 comments on commit 340cf50

Please sign in to comment.