func (c *snapshotter) snapshot(ctx context.Context, withChanged bool) (*snapshot, error) {
	var err error
	s := snapshot{}
	s.CommitSha, err = c.CommitFunc(ctx)
	if err != nil {
		return nil, err
	}
	newFiles, modifiedFiles, err := c.UncommittedFilesFunc(ctx)
	if err != nil {
		return nil, err
	}
	if withChanged {
		newFiles = append(newFiles, modifiedFiles...)
	}
	for _, file := range newFiles {
		hash, err := c.HashFunc(file)
		if err != nil {
			return nil, err
		}
		s.UncommittedFiles = append(s.UncommittedFiles, uncommittedFile{
			Path: file,
			MD5:  hash,
		})
	}
	s.loadMap()
	return &s, nil
}
snapshot takes a snapshot of the current state.
withChanged dictates whether we should include modified files in the snapshot (vs just new files).
package cache
import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"github.com/samsarahq/taskrunner/shell"
)
type snapshotter struct {
	CommitFunc func(context.Context) (sha string, err error)
	DiffFunc func(context.Context, string) (diffFiles []string, err error)
	UncommittedFilesFunc func(context.Context) (newFiles []string, modifiedFiles []string, err error)
	HashFunc func(string) (hash string, err error)
}
type snapshot struct {
	CommitSha string `json:"commitSha"`
	UncommittedFiles []uncommittedFile `json:"uncommittedFiles"`
	uncommittedFilesMap map[string]string
}
func newSnapshotter(shellRun shell.ShellRun) *snapshotter {
	client := gitClient{shellRun: shellRun}
	return &snapshotter{
		DiffFunc:             client.diff,
		CommitFunc:           client.currentCommit,
		UncommittedFilesFunc: client.uncomittedFiles,
		HashFunc:             hashSum,
	}
}
func (c *snapshotter) Diff(ctx context.Context, previous *snapshot) ([]string, error) {
	current, err := c.snapshot(ctx, false)
	if err != nil {
		return nil, err
	}
	gitDiffFiles, err := c.DiffFunc(ctx, previous.CommitSha)
	if err != nil {
		return nil, err
	}
	var modifiedFiles []string
	for _, f := range gitDiffFiles {
		if previous.hashFor(f) == "" {
			modifiedFiles = append(modifiedFiles, f)
		}
	}
	for _, file := range previous.UncommittedFiles {
		md5 := current.hashFor(file.Path)
		if md5 == "" {
			md5, err = c.HashFunc(file.Path)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Warning: unable to hash file %s (error: %v)\n", file.Path, err)
			}
		}
		if md5 == "" || md5 != file.MD5 {
			modifiedFiles = append(gitDiffFiles, file.Path)
		}
	}
	for _, file := range current.UncommittedFiles {
		if sha := previous.hashFor(file.Path); sha == "" || sha != file.MD5 {
			modifiedFiles = append(gitDiffFiles, file.Path)
		}
	}
	return modifiedFiles, err
}