Skip to content

Commit

Permalink
HashFromPathname
Browse files Browse the repository at this point in the history
  • Loading branch information
Karrick S. McDermott committed Jul 15, 2017
1 parent 911cd22 commit 9449c59
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 0 deletions.
93 changes: 93 additions & 0 deletions internal/fs/hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package fs

import (
"crypto/sha256"
"fmt"
"io"
"os"
"strconv"

"github.com/pkg/errors"
)

// HashFromPathname returns a hash of the specified file or directory, ignoring
// all file system objects named, `vendor` and their descendants. This function
// follows symbolic links.
func HashFromPathname(pathname string) (hash string, err error) {
fi, err := os.Stat(pathname)
if err != nil {
return "", errors.Wrap(err, "could not stat")
}
if fi.IsDir() {
return hashFromDirectory(pathname, fi)
}
return hashFromFile(pathname, fi)
}

func hashFromFile(pathname string, fi os.FileInfo) (hash string, err error) {
fh, err := os.Open(pathname)
if err != nil {
return "", errors.Wrap(err, "could not open")
}
defer func() {
err = errors.Wrap(fh.Close(), "could not close")
}()

h := sha256.New()
_, _ = h.Write([]byte(strconv.FormatInt(fi.Size(), 10)))

if _, err = io.Copy(h, fh); err != nil {
err = errors.Wrap(err, "could not read file")
return
}

hash = fmt.Sprintf("%x", h.Sum(nil))
return
}

func hashFromDirectory(pathname string, fi os.FileInfo) (hash string, err error) {
const maxFileInfos = 32

fh, err := os.Open(pathname)
if err != nil {
return hash, errors.Wrap(err, "could not open")
}
defer func() {
err = errors.Wrap(fh.Close(), "could not close")
}()

h := sha256.New()

// NOTE: Chunk through file system objects to prevent allocating too much
// memory for directories with tens of thousands of child objects.
for {
var children []os.FileInfo
var childHash string

children, err = fh.Readdir(maxFileInfos)
if err != nil {
if err == io.EOF {
err = nil
break
}
return hash, errors.Wrap(err, "could not read directory")
}
for _, child := range children {
switch child.Name() {
case ".", "..", "vendor":
// skip
default:
childPathname := pathname + string(os.PathSeparator) + child.Name()
if childHash, err = HashFromPathname(childPathname); err != nil {
err = errors.Wrap(err, "could not compute hash from pathname")
return
}
_, _ = h.Write([]byte(childPathname))
_, _ = h.Write([]byte(childHash))
}
}
}

hash = fmt.Sprintf("%x", h.Sum(nil))
return
}
27 changes: 27 additions & 0 deletions internal/fs/hash_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package fs

import (
"testing"
)

func TestHashFromPathnameWithFile(t *testing.T) {
actual, err := HashFromPathname("testdata/blob")
if err != nil {
t.Fatal(err)
}
expected := "825dc11fe41d8f604ab48a8cd6cecf304005bd82fd0228a6e411e992d4d03a08"
if actual != expected {
t.Errorf("Actual:\n\t%#q\nExpected:\n\t%#q", actual, expected)
}
}

func TestHashFromPathnameWithDirectory(t *testing.T) {
actual, err := HashFromPathname("testdata/recursive")
if err != nil {
t.Fatal(err)
}
expected := "9b3a1f1f63c0c54860799cc5464a3c380a697a3ec49ca103a62d9c09ad9fedf8"
if actual != expected {
t.Errorf("Actual:\n\t%#q\nExpected:\n\t%#q", actual, expected)
}
}
7 changes: 7 additions & 0 deletions internal/fs/testdata/blob
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fjdkal;fdjskc
xzc
axc
fdsf
adsf
das
fd
7 changes: 7 additions & 0 deletions internal/fs/testdata/recursive/blob
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fjdkal;fdjskc
xzc
axc
fdsf
adsf
das
fd
Empty file.
3 changes: 3 additions & 0 deletions internal/fs/testdata/recursive/foo/bar
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fjdsakl;fd
vcafcds
vca
1 change: 1 addition & 0 deletions internal/fs/testdata/recursive/vendor/skip1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
this file ought to be skipped
Empty file.

1 comment on commit 9449c59

@sdboyer
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make a PR for this, so we can discuss? πŸ™ πŸ˜„

Please sign in to comment.