<commit_before>// Package watcher is used for watching files and directories
// for automatic recompilation and restart of app on change
// when in development mode.
package watcher

import (
	"os"
	"path/filepath"
	"sync"

	"github.com/colegion/goal/utils/log"

	"gopkg.in/fsnotify.v1"
)

// Type is a watcher type that allows registering new
// pattern - actions pairs.
type Type struct {
	mu    sync.Mutex
	files map[string]bool
}

// NewType allocates and returns a new instance of watcher Type.
func NewType() *Type {
	return &Type{
		files: map[string]bool{},
	}
}

// Listen gets a pattern and a function. The function will be executed
// when files matching the pattern will be modified.
func (t *Type) Listen(pattern string, fn func()) *fsnotify.Watcher {
	// Create a new watcher.
	w, err := fsnotify.NewWatcher()
	log.AssertNil(err)

	// Find directories matching the pattern.
	ds := glob(pattern)

	// Add the files to the watcher.
	for i := range ds {
		log.Trace.Printf(`Adding "%s" to the list of watched directories...`, ds[i])
		err := w.Add(ds[i])
		if err != nil {
			log.Warn.Println(err)
		}
	}

	// Start watching process.
	go t.NotifyOnUpdate(filepath.ToSlash(pattern), w, fn)
	return w
}

// ListenFile is equivalent of Listen but for files.
// If file is added using ListenFile and the same file
// is withing a pattern of Listen, only the first one
// will trigger restarts.
// I.e. we have the following calls:
//	w.Listen("./", fn1)
//	w.ListenFile("./goal.yml", fn2)
// If "goal.yml" file is modified fn2 will be triggered.
// fn1 may be triggered by changes in any file inside
// "./" directory except "goal.yml".
func (t *Type) ListenFile(path string, fn func()) *fsnotify.Watcher {
	// Create a new watcher.
	w, err := fsnotify.NewWatcher()
	log.AssertNil(err)

	// Watch a directory instead of file.
	// See issue #17 of fsnotify to find out more
	// why we do this.
	dir := filepath.Dir(path)
	w.Add(dir)

	// Clean path and replace back slashes
	// to the normal ones.
	path = filepath.ToSlash(path)

	// Start watching process.
	t.files[path] = true
	go t.NotifyOnUpdate(path, w, fn)
	return w
}

// NotifyOnUpdate starts the function every time a file change
// event is received. Start it as a goroutine.
func (t *Type) NotifyOnUpdate(pattern string, watcher *fsnotify.Watcher, fn func()) {
	for {
		select {
		case ev := <-watcher.Events:
			// Convert path to the Linux format.
			name := filepath.ToSlash(ev.Name)

			// Make sure this is the exact event type that
			// requires a restart.
			if !restartRequired(ev) {
				continue
			}

			// If this is a directory watcher, but a file that was registered
			// with ListenFile has been modified,
			// ignore this event.
			if !t.files[pattern] && t.files[name] {
				continue
			}

			// If this is a single file watcher, make sure this is
			// exactly the file that should be watched, not
			// some other.
			if t.files[pattern] && name != pattern {
				continue
			}

			// Trigger the registered functions.
			t.mu.Lock()
			fn()
			t.mu.Unlock()
		case <-watcher.Errors:
			return
		}
	}
}

// restartRequired checks whether event indicates a file
// has been modified. If so, it returns true.
func restartRequired(event fsnotify.Event) bool {
	// Do not restart if "./bin" directory is modified.
	// TODO: make this configurable.
	d := filepath.ToSlash(event.Name)
	if d == "./bin" || d == "bin" {
		return false
	}

	if event.Op&fsnotify.Chmod == fsnotify.Chmod {
		return false
	}

	log.Trace.Printf(`FS object "%s" has been modified, restarting...`, event.Name)
	return true
}

// glob returns names of all directories matching pattern or nil.
// The only supported special character is an asterisk at the end.
// It means that the directory is expected to be scanned recursively.
// There is no way for fsnotify to watch individual files (see #17),
// so we support only directories.
// File system errors such as I/O reading are ignored.
func glob(pattern string) (ds []string) {
	// Make sure pattern is not empty.
	l := len(pattern)
	if l == 0 {
		return
	}

	// Check whether we should scan the directory recursively.
	recurs := pattern[l-1] == '*'
	if recurs {
		// Trim the asterisk at the end.
		pattern = pattern[:l-1]
	}

	// Make sure such path exists and it is a directory rather than a file.
	info, err := os.Stat(pattern)
	if err != nil {
		return
	}
	if !info.IsDir() {
		log.Warn.Printf(`"%s" is not a directory, skipping it.`, pattern)
		return
	}

	// If not recursive scan was expected, return the path as is.
	if !recurs {
		ds = append(ds, pattern)
		return // Return as is.
	}

	// Start searching directories recursively.
	filepath.Walk(pattern, func(path string, info os.FileInfo, err error) error {
		// Make sure there are no any errors.
		if err != nil {
			return err
		}

		// Make sure the path represents a directory.
		if !info.IsDir() {
			return nil
		}

		// Add the directory path to the list.
		ds = append(ds, path)
		return nil
	})
	return
}
<commit_msg>Do not watch ./assets/ directory<commit_after>// Package watcher is used for watching files and directories
// for automatic recompilation and restart of app on change
// when in development mode.
package watcher

import (
	"os"
	"path/filepath"
	"sync"

	"github.com/colegion/goal/utils/log"

	"gopkg.in/fsnotify.v1"
)

// Type is a watcher type that allows registering new
// pattern - actions pairs.
type Type struct {
	mu    sync.Mutex
	files map[string]bool
}

// NewType allocates and returns a new instance of watcher Type.
func NewType() *Type {
	return &Type{
		files: map[string]bool{},
	}
}

// Listen gets a pattern and a function. The function will be executed
// when files matching the pattern will be modified.
func (t *Type) Listen(pattern string, fn func()) *fsnotify.Watcher {
	// Create a new watcher.
	w, err := fsnotify.NewWatcher()
	log.AssertNil(err)

	// Find directories matching the pattern.
	ds := glob(pattern)

	// Add the files to the watcher.
	for i := range ds {
		log.Trace.Printf(`Adding "%s" to the list of watched directories...`, ds[i])
		err := w.Add(ds[i])
		if err != nil {
			log.Warn.Println(err)
		}
	}

	// Start watching process.
	go t.NotifyOnUpdate(filepath.ToSlash(pattern), w, fn)
	return w
}

// ListenFile is equivalent of Listen but for files.
// If file is added using ListenFile and the same file
// is withing a pattern of Listen, only the first one
// will trigger restarts.
// I.e. we have the following calls:
//	w.Listen("./", fn1)
//	w.ListenFile("./goal.yml", fn2)
// If "goal.yml" file is modified fn2 will be triggered.
// fn1 may be triggered by changes in any file inside
// "./" directory except "goal.yml".
func (t *Type) ListenFile(path string, fn func()) *fsnotify.Watcher {
	// Create a new watcher.
	w, err := fsnotify.NewWatcher()
	log.AssertNil(err)

	// Watch a directory instead of file.
	// See issue #17 of fsnotify to find out more
	// why we do this.
	dir := filepath.Dir(path)
	w.Add(dir)

	// Clean path and replace back slashes
	// to the normal ones.
	path = filepath.ToSlash(path)

	// Start watching process.
	t.files[path] = true
	go t.NotifyOnUpdate(path, w, fn)
	return w
}

// NotifyOnUpdate starts the function every time a file change
// event is received. Start it as a goroutine.
func (t *Type) NotifyOnUpdate(pattern string, watcher *fsnotify.Watcher, fn func()) {
	for {
		select {
		case ev := <-watcher.Events:
			// Convert path to the Linux format.
			name := filepath.ToSlash(ev.Name)

			// Make sure this is the exact event type that
			// requires a restart.
			if !restartRequired(ev) {
				continue
			}

			// If this is a directory watcher, but a file that was registered
			// with ListenFile has been modified,
			// ignore this event.
			if !t.files[pattern] && t.files[name] {
				continue
			}

			// If this is a single file watcher, make sure this is
			// exactly the file that should be watched, not
			// some other.
			if t.files[pattern] && name != pattern {
				continue
			}

			// Trigger the registered functions.
			t.mu.Lock()
			fn()
			t.mu.Unlock()
		case <-watcher.Errors:
			return
		}
	}
}

// restartRequired checks whether event indicates a file
// has been modified. If so, it returns true.
func restartRequired(event fsnotify.Event) bool {
	// Do not restart if system directories are modified.
	// TODO: make the directories configurable.
	d := filepath.ToSlash(event.Name)
	switch d {
	case "./bin", "bin", "./assets", "assets":
		return false
	}

	if event.Op&fsnotify.Chmod == fsnotify.Chmod {
		return false
	}

	log.Trace.Printf(`FS object "%s" has been modified, restarting...`, event.Name)
	return true
}

// glob returns names of all directories matching pattern or nil.
// The only supported special character is an asterisk at the end.
// It means that the directory is expected to be scanned recursively.
// There is no way for fsnotify to watch individual files (see #17),
// so we support only directories.
// File system errors such as I/O reading are ignored.
func glob(pattern string) (ds []string) {
	// Make sure pattern is not empty.
	l := len(pattern)
	if l == 0 {
		return
	}

	// Check whether we should scan the directory recursively.
	recurs := pattern[l-1] == '*'
	if recurs {
		// Trim the asterisk at the end.
		pattern = pattern[:l-1]
	}

	// Make sure such path exists and it is a directory rather than a file.
	info, err := os.Stat(pattern)
	if err != nil {
		return
	}
	if !info.IsDir() {
		log.Warn.Printf(`"%s" is not a directory, skipping it.`, pattern)
		return
	}

	// If not recursive scan was expected, return the path as is.
	if !recurs {
		ds = append(ds, pattern)
		return // Return as is.
	}

	// Start searching directories recursively.
	filepath.Walk(pattern, func(path string, info os.FileInfo, err error) error {
		// Make sure there are no any errors.
		if err != nil {
			return err
		}

		// Make sure the path represents a directory.
		if !info.IsDir() {
			return nil
		}

		// Add the directory path to the list.
		ds = append(ds, path)
		return nil
	})
	return
}
<|endoftext|>


<commit_before>package compile

import (
	"strconv"

	"github.com/jimmyfrasche/etlite/internal/ast"
	"github.com/jimmyfrasche/etlite/internal/internal/errint"
	"github.com/jimmyfrasche/etlite/internal/token"
	"github.com/jimmyfrasche/etlite/internal/virt"
)

func tblFrom(nm []token.Value) string {
	var s string
	//we assume the escaping, if any, has been applied
	for _, t := range nm {
		s += t.Value
	}
	return s
}

func (c *compiler) compileSQL(s *ast.SQL) {

	//CREATE TABLE ... FROM IMPORT is a special case
	if len(s.Name) > 0 {
		if ln := len(s.Name); ln == 2 || ln > 3 {
			panic(errint.Newf("table name in CREATE TABLE FROM IMPORT must have 1 or 3 tokens, got %d", ln))
		}
		if len(s.Subqueries) != 1 {
			panic(errint.Newf("found %d imports in CREATE TABLE FROM IMPORT but should only have 1", len(s.Subqueries)))
		}

		nm := tblFrom(s.Name)

		ddl, err := s.ToString()
		if err != nil {
			panic(err)
		}

		i := s.Subqueries[0]
		c.compileCreateTableAsImport(nm, ddl, i)
		return
	}

	//regular sql, may or may not have etl subqueries

	//if etl subquery, handle set up
	var tbls []string
	if len(s.Subqueries) > 0 {
		c.push(virt.Savepoint())

		//compile the imports
		tbls = make([]string, len(s.Subqueries))
		for i, imp := range s.Subqueries {
			tbls[i] = "[" + strconv.Itoa(i) + "]"
			c.compileSubImport(imp, tbls[i])
		}

		//rewrite the placeholders to select from our well named tables.
		i := 0
		for j, t := range s.Tokens {
			if t.Kind == token.Placeholder {
				s.Tokens[j] = token.Value{
					Kind:  token.Literal,
					Value: "select * from temp." + tbls[i], //TODO create synthetic tokens
				}
				i++
			}
		}
		if i != len(s.Subqueries) {
			panic(errint.Newf("expected %d placeholders in subquery got %d:\n%v", len(s.Subqueries), i, s))
		}

	}

	q, err := s.ToString()
	if err != nil {
		panic(err)
	}
	c.push(virt.Query(q))

	//if this was an etl subquery, handle teardown
	if len(s.Subqueries) > 0 {
		c.push(virt.DropTempTables(tbls))
		c.push(virt.Release())
	}
}
<commit_msg>not going TODO that<commit_after>package compile

import (
	"strconv"

	"github.com/jimmyfrasche/etlite/internal/ast"
	"github.com/jimmyfrasche/etlite/internal/internal/errint"
	"github.com/jimmyfrasche/etlite/internal/token"
	"github.com/jimmyfrasche/etlite/internal/virt"
)

func tblFrom(nm []token.Value) string {
	var s string
	//we assume the escaping, if any, has been applied
	for _, t := range nm {
		s += t.Value
	}
	return s
}

func (c *compiler) compileSQL(s *ast.SQL) {

	//CREATE TABLE ... FROM IMPORT is a special case
	if len(s.Name) > 0 {
		if ln := len(s.Name); ln == 2 || ln > 3 {
			panic(errint.Newf("table name in CREATE TABLE FROM IMPORT must have 1 or 3 tokens, got %d", ln))
		}
		if len(s.Subqueries) != 1 {
			panic(errint.Newf("found %d imports in CREATE TABLE FROM IMPORT but should only have 1", len(s.Subqueries)))
		}

		nm := tblFrom(s.Name)

		ddl, err := s.ToString()
		if err != nil {
			panic(err)
		}

		i := s.Subqueries[0]
		c.compileCreateTableAsImport(nm, ddl, i)
		return
	}

	//regular sql, may or may not have etl subqueries

	//if etl subquery, handle set up
	var tbls []string
	if len(s.Subqueries) > 0 {
		c.push(virt.Savepoint())

		//compile the imports
		tbls = make([]string, len(s.Subqueries))
		for i, imp := range s.Subqueries {
			tbls[i] = "[" + strconv.Itoa(i) + "]"
			c.compileSubImport(imp, tbls[i])
		}

		//rewrite the placeholders to select from our well named tables.
		i := 0
		for j, t := range s.Tokens {
			if t.Kind == token.Placeholder {
				s.Tokens[j] = token.Value{
					Kind:  token.Literal,
					Value: "select * from temp." + tbls[i],
				}
				i++
			}
		}
		if i != len(s.Subqueries) {
			panic(errint.Newf("expected %d placeholders in subquery got %d:\n%v", len(s.Subqueries), i, s))
		}

	}

	q, err := s.ToString()
	if err != nil {
		panic(err)
	}
	c.push(virt.Query(q))

	//if this was an etl subquery, handle teardown
	if len(s.Subqueries) > 0 {
		c.push(virt.DropTempTables(tbls))
		c.push(virt.Release())
	}
}
<|endoftext|>


<commit_before>package git_repo

import (
	"context"
	"fmt"
	"io/ioutil"
	"path/filepath"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/filemode"
	"github.com/go-git/go-git/v5/plumbing/format/index"

	"github.com/werf/logboek"

	"github.com/werf/werf/pkg/git_repo/check_ignore"
	"github.com/werf/werf/pkg/git_repo/status"
	"github.com/werf/werf/pkg/path_matcher"
	"github.com/werf/werf/pkg/true_git"
	"github.com/werf/werf/pkg/true_git/ls_tree"
	"github.com/werf/werf/pkg/util"
)

type Local struct {
	Base
	Path   string
	GitDir string

	headCommit string
}

func OpenLocalRepo(name, path string) (*Local, error) {
	_, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
	if err != nil {
		if err == git.ErrRepositoryNotExists {
			return nil, nil
		}

		return nil, err
	}

	gitDir, err := true_git.GetRealRepoDir(filepath.Join(path, ".git"))
	if err != nil {
		return nil, fmt.Errorf("unable to get real git repo dir for %s: %s", path, err)
	}

	localRepo, err := newLocal(name, path, gitDir)
	if err != nil {
		return nil, err
	}

	return localRepo, nil
}

func newLocal(name, path, gitDir string) (*Local, error) {
	headCommit, err := getHeadCommit(path)
	if err != nil {
		return nil, fmt.Errorf("unable to get git repo head commit: %s", err)
	}

	local := &Local{
		Base:       Base{Name: name},
		Path:       path,
		GitDir:     gitDir,
		headCommit: headCommit,
	}

	return local, nil
}

func (repo *Local) PlainOpen() (*git.Repository, error) {
	return git.PlainOpen(repo.Path)
}

func (repo *Local) SyncWithOrigin(ctx context.Context) error {
	isShallow, err := repo.IsShallowClone()
	if err != nil {
		return fmt.Errorf("check shallow clone failed: %s", err)
	}

	remoteOriginUrl, err := repo.RemoteOriginUrl(ctx)
	if err != nil {
		return fmt.Errorf("get remote origin failed: %s", err)
	}

	if remoteOriginUrl == "" {
		return fmt.Errorf("git remote origin was not detected")
	}

	return logboek.Context(ctx).Default().LogProcess("Syncing origin branches and tags").DoError(func() error {
		fetchOptions := true_git.FetchOptions{
			Prune:     true,
			PruneTags: true,
			Unshallow: isShallow,
			RefSpecs:  map[string]string{"origin": "+refs/heads/*:refs/remotes/origin/*"},
		}

		if err := true_git.Fetch(ctx, repo.Path, fetchOptions); err != nil {
			return fmt.Errorf("fetch failed: %s", err)
		}

		return nil
	})
}

func (repo *Local) FetchOrigin(ctx context.Context) error {
	isShallow, err := repo.IsShallowClone()
	if err != nil {
		return fmt.Errorf("check shallow clone failed: %s", err)
	}

	remoteOriginUrl, err := repo.RemoteOriginUrl(ctx)
	if err != nil {
		return fmt.Errorf("get remote origin failed: %s", err)
	}

	if remoteOriginUrl == "" {
		return fmt.Errorf("git remote origin was not detected")
	}

	return logboek.Context(ctx).Default().LogProcess("Fetching origin").DoError(func() error {
		fetchOptions := true_git.FetchOptions{
			Unshallow: isShallow,
			RefSpecs:  map[string]string{"origin": "+refs/heads/*:refs/remotes/origin/*"},
		}

		if err := true_git.Fetch(ctx, repo.Path, fetchOptions); err != nil {
			return fmt.Errorf("fetch failed: %s", err)
		}

		return nil
	})
}

func (repo *Local) IsShallowClone() (bool, error) {
	return true_git.IsShallowClone(repo.Path)
}

func (repo *Local) CreateDetachedMergeCommit(ctx context.Context, fromCommit, toCommit string) (string, error) {
	return repo.createDetachedMergeCommit(ctx, repo.GitDir, repo.Path, repo.getRepoWorkTreeCacheDir(repo.getRepoID()), fromCommit, toCommit)
}

func (repo *Local) GetMergeCommitParents(_ context.Context, commit string) ([]string, error) {
	return repo.getMergeCommitParents(repo.GitDir, commit)
}

type LsTreeOptions struct {
	Commit        string
	UseHeadCommit bool
	Strict        bool
}

func (repo *Local) LsTree(ctx context.Context, pathMatcher path_matcher.PathMatcher, opts LsTreeOptions) (*ls_tree.Result, error) {
	repository, err := git.PlainOpenWithOptions(repo.Path, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
	if err != nil {
		return nil, fmt.Errorf("cannot open repo %s: %s", repo.Path, err)
	}

	var commit string
	if opts.UseHeadCommit {
		commit = repo.headCommit
	} else if opts.Commit == "" {
		panic(fmt.Sprintf("no commit specified for LsTree procedure: specify Commit or HeadCommit"))
	} else {
		commit = opts.Commit
	}

	return ls_tree.LsTree(ctx, repository, commit, pathMatcher, opts.Strict)
}

func (repo *Local) Status(ctx context.Context, pathMatcher path_matcher.PathMatcher) (*status.Result, error) {
	repository, err := git.PlainOpenWithOptions(repo.Path, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
	if err != nil {
		return nil, fmt.Errorf("cannot open repo %s: %s", repo.Path, err)
	}

	return status.Status(ctx, repository, repo.Path, pathMatcher)
}

func (repo *Local) CheckIgnore(ctx context.Context, paths []string) (*check_ignore.Result, error) {
	repository, err := git.PlainOpenWithOptions(repo.Path, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
	if err != nil {
		return nil, fmt.Errorf("cannot open repo %s: %s", repo.Path, err)
	}

	return check_ignore.CheckIgnore(ctx, repository, repo.Path, paths)
}

func (repo *Local) IsEmpty(ctx context.Context) (bool, error) {
	return repo.isEmpty(ctx, repo.Path)
}

func (repo *Local) IsAncestor(_ context.Context, ancestorCommit, descendantCommit string) (bool, error) {
	return true_git.IsAncestor(ancestorCommit, descendantCommit, repo.GitDir)
}

func (repo *Local) RemoteOriginUrl(ctx context.Context) (string, error) {
	return repo.remoteOriginUrl(repo.Path)
}

func (repo *Local) HeadCommit(_ context.Context) (string, error) {
	return repo.headCommit, nil
}

func (repo *Local) CreatePatch(ctx context.Context, opts PatchOptions) (Patch, error) {
	return repo.createPatch(ctx, repo.Path, repo.GitDir, repo.getRepoID(), repo.getRepoWorkTreeCacheDir(repo.getRepoID()), opts)
}

func (repo *Local) CreateArchive(ctx context.Context, opts ArchiveOptions) (Archive, error) {
	return repo.createArchive(ctx, repo.Path, repo.GitDir, repo.getRepoID(), repo.getRepoWorkTreeCacheDir(repo.getRepoID()), opts)
}

func (repo *Local) Checksum(ctx context.Context, opts ChecksumOptions) (checksum Checksum, err error) {
	logboek.Context(ctx).Debug().LogProcess("Calculating checksum").Do(func() {
		checksum, err = repo.checksumWithLsTree(ctx, repo.Path, repo.GitDir, repo.getRepoWorkTreeCacheDir(repo.getRepoID()), opts)
	})

	return checksum, err
}

func (repo *Local) CheckAndReadCommitSymlink(ctx context.Context, path string, commit string) (bool, []byte, error) {
	return repo.checkAndReadSymlink(ctx, repo.Path, repo.GitDir, commit, path)
}

func (repo *Local) IsCommitExists(ctx context.Context, commit string) (bool, error) {
	return repo.isCommitExists(ctx, repo.Path, repo.GitDir, commit)
}

func (repo *Local) TagsList(ctx context.Context) ([]string, error) {
	return repo.tagsList(repo.Path)
}

func (repo *Local) RemoteBranchesList(ctx context.Context) ([]string, error) {
	return repo.remoteBranchesList(repo.Path)
}

func (repo *Local) getRepoID() string {
	absPath, err := filepath.Abs(repo.Path)
	if err != nil {
		panic(err) // stupid interface of filepath.Abs
	}

	fullPath := filepath.Clean(absPath)
	return util.Sha256Hash(fullPath)
}

func (repo *Local) getRepoWorkTreeCacheDir(repoID string) string {
	return filepath.Join(GetWorkTreeCacheDir(), "local", repoID)
}

func (repo *Local) IsCommitFileExists(ctx context.Context, commit, path string) (bool, error) {
	return repo.isFileExists(ctx, repo.Path, repo.GitDir, commit, path)
}

func (repo *Local) IsCommitDirectoryExists(ctx context.Context, dir string, commit string) (bool, error) {
	if paths, err := repo.GetCommitFilePathList(ctx, commit); err != nil {
		return false, fmt.Errorf("unable to get file path list from the local git repo commit %s: %s", commit, err)
	} else {
		cleanDirPath := filepath.ToSlash(filepath.Clean(dir))
		for _, path := range paths {
			isSubpath := util.IsSubpathOfBasePath(cleanDirPath, path)
			if isSubpath {
				return true, nil
			}
		}

		return false, nil
	}
}

func (repo *Local) GetCommitFilePathList(ctx context.Context, commit string) ([]string, error) {
	result, err := repo.LsTree(ctx, path_matcher.NewGitMappingPathMatcher("", nil, nil, true), LsTreeOptions{
		Commit: commit,
		Strict: true,
	})
	if err != nil {
		return nil, err
	}

	var res []string
	if err := result.Walk(func(lsTreeEntry *ls_tree.LsTreeEntry) error {
		res = append(res, lsTreeEntry.FullFilepath)
		return nil
	}); err != nil {
		return nil, err
	}

	return res, nil
}

func (repo *Local) ReadCommitFile(ctx context.Context, commit, path string) ([]byte, error) {
	return repo.readFile(ctx, repo.Path, repo.GitDir, commit, path)
}

func (repo *Local) IsIndexFileExists(_ context.Context, path string) (bool, error) {
	repository, err := git.PlainOpenWithOptions(repo.Path, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
	if err != nil {
		return false, fmt.Errorf("cannot open repo %s: %s", repo.Path, err)
	}

	realpath, err := repo.getIndexEntryRealpath(repository, path)
	if err != nil {
		return false, fmt.Errorf("error getting realpath for %q path: %s", path, err)
	}

	if _, err := repo.getIndexEntry(repository, path); err == index.ErrEntryNotFound {
		return false, nil
	} else if err != nil {
		return false, fmt.Errorf("error getting repo index file %q: %s", realpath, err)
	}

	return true, nil
}

// TODO submodules are not supported
func (repo *Local) GetIndexFilePathList(_ context.Context) ([]string, error) {
	var res []string

	repository, err := git.PlainOpenWithOptions(repo.Path, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
	if err != nil {
		return nil, fmt.Errorf("cannot open repo %s: %s", repo.Path, err)
	}

	i, err := repository.Storer.Index()
	if err != nil {
		return nil, fmt.Errorf("cannot get repo index: %s", err)
	}

	for _, entry := range i.Entries {
		res = append(res, entry.Name)
	}

	return res, nil
}

func (repo *Local) ReadIndexFile(_ context.Context, path string) ([]byte, error) {
	repository, err := git.PlainOpenWithOptions(repo.Path, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
	if err != nil {
		return nil, fmt.Errorf("cannot open repo %s: %s", repo.Path, err)
	}

	realpath, err := repo.getIndexEntryRealpath(repository, path)
	if err != nil {
		return nil, fmt.Errorf("error getting realpath for %q path: %s", path, err)
	}

	return repo.readIndexEntryData(repository, realpath)
}

func (repo *Local) getIndexEntryRealpath(repository *git.Repository, path string) (string, error) {
	if entry, err := repo.getIndexEntry(repository, path); err != nil {
		if err == index.ErrEntryNotFound {
			return path, nil
		}

		return "", err
	} else if entry.Mode == filemode.Symlink {
		data, err := repo.readIndexEntryData(repository, path)
		if err != nil {
			return "", err
		}

		return string(data), err
	}

	return path, nil
}

func (repo *Local) readIndexEntryData(repository *git.Repository, path string) ([]byte, error) {
	entry, err := repo.getIndexEntry(repository, path)
	if err != nil {
		return nil, err
	}

	obj, err := repository.Storer.EncodedObject(plumbing.BlobObject, entry.Hash)
	if err != nil {
		return nil, fmt.Errorf("cannot encode repo blob object: %s", err)
	}

	r, err := obj.Reader()
	if err != nil {
		return nil, fmt.Errorf("cannot get reader: %s", err)
	}

	data, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}

	return data, nil
}

func (repo *Local) getIndexEntry(repository *git.Repository, path string) (*index.Entry, error) {
	i, err := repository.Storer.Index()
	if err != nil {
		return nil, fmt.Errorf("cannot get repo index: %s", err)
	}

	return i.Entry(path)
}
<commit_msg>Revert "[git_repo] Add index file methods"<commit_after>package git_repo

import (
	"context"
	"fmt"
	"path/filepath"

	"github.com/go-git/go-git/v5"

	"github.com/werf/logboek"

	"github.com/werf/werf/pkg/git_repo/check_ignore"
	"github.com/werf/werf/pkg/git_repo/status"
	"github.com/werf/werf/pkg/path_matcher"
	"github.com/werf/werf/pkg/true_git"
	"github.com/werf/werf/pkg/true_git/ls_tree"
	"github.com/werf/werf/pkg/util"
)

type Local struct {
	Base
	Path   string
	GitDir string

	headCommit string
}

func OpenLocalRepo(name, path string) (*Local, error) {
	_, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
	if err != nil {
		if err == git.ErrRepositoryNotExists {
			return nil, nil
		}

		return nil, err
	}

	gitDir, err := true_git.GetRealRepoDir(filepath.Join(path, ".git"))
	if err != nil {
		return nil, fmt.Errorf("unable to get real git repo dir for %s: %s", path, err)
	}

	localRepo, err := newLocal(name, path, gitDir)
	if err != nil {
		return nil, err
	}

	return localRepo, nil
}

func newLocal(name, path, gitDir string) (*Local, error) {
	headCommit, err := getHeadCommit(path)
	if err != nil {
		return nil, fmt.Errorf("unable to get git repo head commit: %s", err)
	}

	local := &Local{
		Base:       Base{Name: name},
		Path:       path,
		GitDir:     gitDir,
		headCommit: headCommit,
	}

	return local, nil
}

func (repo *Local) PlainOpen() (*git.Repository, error) {
	return git.PlainOpen(repo.Path)
}

func (repo *Local) SyncWithOrigin(ctx context.Context) error {
	isShallow, err := repo.IsShallowClone()
	if err != nil {
		return fmt.Errorf("check shallow clone failed: %s", err)
	}

	remoteOriginUrl, err := repo.RemoteOriginUrl(ctx)
	if err != nil {
		return fmt.Errorf("get remote origin failed: %s", err)
	}

	if remoteOriginUrl == "" {
		return fmt.Errorf("git remote origin was not detected")
	}

	return logboek.Context(ctx).Default().LogProcess("Syncing origin branches and tags").DoError(func() error {
		fetchOptions := true_git.FetchOptions{
			Prune:     true,
			PruneTags: true,
			Unshallow: isShallow,
			RefSpecs:  map[string]string{"origin": "+refs/heads/*:refs/remotes/origin/*"},
		}

		if err := true_git.Fetch(ctx, repo.Path, fetchOptions); err != nil {
			return fmt.Errorf("fetch failed: %s", err)
		}

		return nil
	})
}

func (repo *Local) FetchOrigin(ctx context.Context) error {
	isShallow, err := repo.IsShallowClone()
	if err != nil {
		return fmt.Errorf("check shallow clone failed: %s", err)
	}

	remoteOriginUrl, err := repo.RemoteOriginUrl(ctx)
	if err != nil {
		return fmt.Errorf("get remote origin failed: %s", err)
	}

	if remoteOriginUrl == "" {
		return fmt.Errorf("git remote origin was not detected")
	}

	return logboek.Context(ctx).Default().LogProcess("Fetching origin").DoError(func() error {
		fetchOptions := true_git.FetchOptions{
			Unshallow: isShallow,
			RefSpecs:  map[string]string{"origin": "+refs/heads/*:refs/remotes/origin/*"},
		}

		if err := true_git.Fetch(ctx, repo.Path, fetchOptions); err != nil {
			return fmt.Errorf("fetch failed: %s", err)
		}

		return nil
	})
}

func (repo *Local) IsShallowClone() (bool, error) {
	return true_git.IsShallowClone(repo.Path)
}

func (repo *Local) CreateDetachedMergeCommit(ctx context.Context, fromCommit, toCommit string) (string, error) {
	return repo.createDetachedMergeCommit(ctx, repo.GitDir, repo.Path, repo.getRepoWorkTreeCacheDir(repo.getRepoID()), fromCommit, toCommit)
}

func (repo *Local) GetMergeCommitParents(_ context.Context, commit string) ([]string, error) {
	return repo.getMergeCommitParents(repo.GitDir, commit)
}

type LsTreeOptions struct {
	Commit        string
	UseHeadCommit bool
	Strict        bool
}

func (repo *Local) LsTree(ctx context.Context, pathMatcher path_matcher.PathMatcher, opts LsTreeOptions) (*ls_tree.Result, error) {
	repository, err := git.PlainOpenWithOptions(repo.Path, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
	if err != nil {
		return nil, fmt.Errorf("cannot open repo %s: %s", repo.Path, err)
	}

	var commit string
	if opts.UseHeadCommit {
		commit = repo.headCommit
	} else if opts.Commit == "" {
		panic(fmt.Sprintf("no commit specified for LsTree procedure: specify Commit or HeadCommit"))
	} else {
		commit = opts.Commit
	}

	return ls_tree.LsTree(ctx, repository, commit, pathMatcher, opts.Strict)
}

func (repo *Local) Status(ctx context.Context, pathMatcher path_matcher.PathMatcher) (*status.Result, error) {
	repository, err := git.PlainOpenWithOptions(repo.Path, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
	if err != nil {
		return nil, fmt.Errorf("cannot open repo %s: %s", repo.Path, err)
	}

	return status.Status(ctx, repository, repo.Path, pathMatcher)
}

func (repo *Local) CheckIgnore(ctx context.Context, paths []string) (*check_ignore.Result, error) {
	repository, err := git.PlainOpenWithOptions(repo.Path, &git.PlainOpenOptions{EnableDotGitCommonDir: true})
	if err != nil {
		return nil, fmt.Errorf("cannot open repo %s: %s", repo.Path, err)
	}

	return check_ignore.CheckIgnore(ctx, repository, repo.Path, paths)
}

func (repo *Local) IsEmpty(ctx context.Context) (bool, error) {
	return repo.isEmpty(ctx, repo.Path)
}

func (repo *Local) IsAncestor(_ context.Context, ancestorCommit, descendantCommit string) (bool, error) {
	return true_git.IsAncestor(ancestorCommit, descendantCommit, repo.GitDir)
}

func (repo *Local) RemoteOriginUrl(ctx context.Context) (string, error) {
	return repo.remoteOriginUrl(repo.Path)
}

func (repo *Local) HeadCommit(_ context.Context) (string, error) {
	return repo.headCommit, nil
}

func (repo *Local) CreatePatch(ctx context.Context, opts PatchOptions) (Patch, error) {
	return repo.createPatch(ctx, repo.Path, repo.GitDir, repo.getRepoID(), repo.getRepoWorkTreeCacheDir(repo.getRepoID()), opts)
}

func (repo *Local) CreateArchive(ctx context.Context, opts ArchiveOptions) (Archive, error) {
	return repo.createArchive(ctx, repo.Path, repo.GitDir, repo.getRepoID(), repo.getRepoWorkTreeCacheDir(repo.getRepoID()), opts)
}

func (repo *Local) Checksum(ctx context.Context, opts ChecksumOptions) (checksum Checksum, err error) {
	logboek.Context(ctx).Debug().LogProcess("Calculating checksum").Do(func() {
		checksum, err = repo.checksumWithLsTree(ctx, repo.Path, repo.GitDir, repo.getRepoWorkTreeCacheDir(repo.getRepoID()), opts)
	})

	return checksum, err
}

func (repo *Local) CheckAndReadCommitSymlink(ctx context.Context, path string, commit string) (bool, []byte, error) {
	return repo.checkAndReadSymlink(ctx, repo.Path, repo.GitDir, commit, path)
}

func (repo *Local) IsCommitExists(ctx context.Context, commit string) (bool, error) {
	return repo.isCommitExists(ctx, repo.Path, repo.GitDir, commit)
}

func (repo *Local) TagsList(ctx context.Context) ([]string, error) {
	return repo.tagsList(repo.Path)
}

func (repo *Local) RemoteBranchesList(ctx context.Context) ([]string, error) {
	return repo.remoteBranchesList(repo.Path)
}

func (repo *Local) getRepoID() string {
	absPath, err := filepath.Abs(repo.Path)
	if err != nil {
		panic(err) // stupid interface of filepath.Abs
	}

	fullPath := filepath.Clean(absPath)
	return util.Sha256Hash(fullPath)
}

func (repo *Local) getRepoWorkTreeCacheDir(repoID string) string {
	return filepath.Join(GetWorkTreeCacheDir(), "local", repoID)
}

func (repo *Local) IsCommitFileExists(ctx context.Context, commit, path string) (bool, error) {
	return repo.isFileExists(ctx, repo.Path, repo.GitDir, commit, path)
}

func (repo *Local) IsCommitDirectoryExists(ctx context.Context, dir string, commit string) (bool, error) {
	if paths, err := repo.GetCommitFilePathList(ctx, commit); err != nil {
		return false, fmt.Errorf("unable to get file path list from the local git repo commit %s: %s", commit, err)
	} else {
		cleanDirPath := filepath.ToSlash(filepath.Clean(dir))
		for _, path := range paths {
			isSubpath := util.IsSubpathOfBasePath(cleanDirPath, path)
			if isSubpath {
				return true, nil
			}
		}

		return false, nil
	}
}

func (repo *Local) GetCommitFilePathList(ctx context.Context, commit string) ([]string, error) {
	result, err := repo.LsTree(ctx, path_matcher.NewGitMappingPathMatcher("", nil, nil, true), LsTreeOptions{
		Commit: commit,
		Strict: true,
	})
	if err != nil {
		return nil, err
	}

	var res []string
	if err := result.Walk(func(lsTreeEntry *ls_tree.LsTreeEntry) error {
		res = append(res, lsTreeEntry.FullFilepath)
		return nil
	}); err != nil {
		return nil, err
	}

	return res, nil
}

func (repo *Local) ReadCommitFile(ctx context.Context, commit, path string) ([]byte, error) {
	return repo.readFile(ctx, repo.Path, repo.GitDir, commit, path)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"image"
	"image/color"
	"image/png"
	"math/cmplx"
	"os"

	"github.com/krasoffski/gomill/htcmap"
)

func main() {
	const (
		xmin, ymin    = -2.3, -1.2
		xmax, ymax    = +1.2, +1.2
		width, height = 4000, 3000
		// step          = 1
	)

	// stepx := step * (xmax - xmin) / width
	// stepy := step * (ymax - ymin) / height
	img := image.NewRGBA(image.Rect(0, 0, width, height))
	for py := 0; py < height; py++ {
		y0 := float64(py)/height*(ymax-ymin) + ymin
		for px := 0; px < width; px++ {
			x0 := float64(px)/width*(xmax-xmin) + xmin
			c := mandelbrot(complex(x0, y0))
			// r0, g0, b0, _ := mandelbrot(complex(x0, y0)).RGBA()
			// r1, g1, b1, _ := mandelbrot(complex(x0-stepx, y0-stepy)).RGBA()
			// r2, g2, b2, _ := mandelbrot(complex(x0+stepx, y0-stepy)).RGBA()
			// r3, g3, b3, _ := mandelbrot(complex(x0+stepx, y0+stepy)).RGBA()
			// r4, g4, b4, _ := mandelbrot(complex(x0-stepx, y0+stepy)).RGBA()
			// r := uint16(math.Sqrt(float64(r0*r0+r1*r1+r2*r2+r3*r3+r4*r4) / 5))
			// g := uint16(math.Sqrt(float64(g0*g0+g1*g1+g2*g2+g3*g3+g4*g4) / 5))
			// b := uint16(math.Sqrt(float64(b0*b0+b1*b1+b2*b2+b3*b3+b4*b4) / 5))
			// r := uint16((r0 + r1 + r2 + r3 + r4) / 5)
			// g := uint16((g0 + g1 + g2 + g3 + g4) / 5)
			// b := uint16((b0 + b1 + b2 + b3 + b4) / 5)
			img.Set(px, py, c)
		}
	}
	if err := png.Encode(os.Stdout, img); err != nil {
		fmt.Fprintf(os.Stderr, "error encoding png: %s", err)
	}
}

func mandelbrot(z complex128) color.Color {
	const iterations = 255
	const contrast = 15

	var v complex128
	for n := uint8(0); n < iterations; n++ {
		v = v*v + z
		vAbs := cmplx.Abs(v)
		if vAbs > 2 {
			// smooth := float64(n) + 1 - math.Log(math.Log(vAbs))/math.Log(2)
			r, g, b := htcmap.AsUInt8(float64(n*contrast), 0, iterations)
			return color.RGBA{r, g, b, 255}
		}
	}
	return color.Black
}
<commit_msg>Working prototype of supersampling (2x2).<commit_after>package main

import (
	"fmt"
	"image"
	"image/color"
	"image/png"
	"math/cmplx"
	"os"

	"github.com/krasoffski/gomill/htcmap"
)

func main() {
	const (
		xmin, ymin        = -2.2, -1.2
		xmax, ymax        = +1.2, +1.2
		width, height     = 1536, 1024
		widthSS, heightSS = width * 2, height * 2
	)

	xCord := func(x int) float64 {
		return float64(x)/widthSS*(xmax-xmin) + xmin
	}

	yCord := func(y int) float64 {
		return float64(y)/heightSS*(ymax-ymin) + ymin
	}

	img := image.NewRGBA(image.Rect(0, 0, width, height))
	for py := 0; py < heightSS; py += 2 {
		for px := 0; px < widthSS; px += 2 {
			x0, x1, y0, y1 := xCord(px), xCord(px+1), yCord(py), yCord(py+1)
			r0, g0, b0, _ := mandelbrot(complex(x0, y0)).RGBA()
			r1, g1, b1, _ := mandelbrot(complex(x1, y0)).RGBA()
			r2, g2, b2, _ := mandelbrot(complex(x0, y1)).RGBA()
			r3, g3, b3, _ := mandelbrot(complex(x1, y1)).RGBA()

			// r := uint16(math.Sqrt(float64(r0*r0+r1*r1+r2*r2+r3*r3) / 4))
			// g := uint16(math.Sqrt(float64(g0*g0+g1*g1+g2*g2+g3*g3) / 4))
			// b := uint16(math.Sqrt(float64(b0*b0+b1*b1+b2*b2+b3*b3) / 4))
			r := uint16((r0 + r1 + r2 + r3) / 4)
			g := uint16((g0 + g1 + g2 + g3) / 4)
			b := uint16((b0 + b1 + b2 + b3) / 4)
			img.Set(px/2, py/2, color.RGBA64{r, g, b, 0xFFFF})
		}
	}
	if err := png.Encode(os.Stdout, img); err != nil {
		fmt.Fprintf(os.Stderr, "error encoding png: %s", err)
	}
}

func mandelbrot(z complex128) color.Color {
	const iterations = 255
	const contrast = 15

	var v complex128
	for n := uint8(0); n < iterations; n++ {
		v = v*v + z
		vAbs := cmplx.Abs(v)
		if vAbs > 2 {
			// smooth := float64(n) + 1 - math.Log(math.Log(vAbs))/math.Log(2)
			r, g, b := htcmap.AsUInt8(float64(n*contrast), 0, iterations)
			return color.RGBA{r, g, b, 255}
		}
	}
	return color.Black
}
<|endoftext|>


<commit_before>package rpc

import (
	"encoding/json"

	"github.com/gopherjs/websocket"
)

type Call struct {
	ServiceMethod string
	Args, Reply   interface{}
	Error         error
	Done          chan *Call
}

type request struct {
	Method string         `json:"method"`
	ID     uint           `json:"id"`
	Params [1]interface{} `json:"params"`
}

type Client struct {
	ws     *websocket.Websocket
	nextID uint
	reqs   map[uint]func(json.RawMessage, error)
}

func Dial(addr string) (*Client, error) {
	w, err := websocket.New(addr)
	if err != nil {
		return nil, err
	}
	reqs := make(map[uint]func(json.RawMessage, error))
	w.AddEventListener("message", false, func(e *js.Object) {
		var (
			r request
			m json.RawMessage
		)
		r.Params[0] = &m
		err := json.UnmarshalString(e.Get("data").String(), &r)
		f, ok := reqs[r.ID]
		if ok {
			delete(reqs, r.ID)
			f(m, err)
		}
	})
	return &Client{
		ws:   w,
		reqs: reqs,
	}, nil
}

func (c *Client) Call(method string, args interface{}, reply interface{}) error {
	call := <-c.Go(method, args, reply, make(chan *Call, 1)).Done
	return call.Error
}

func (c *Client) Close() error {
	return c.w.Close()
}

func (c *Client) Go(method string, args interface{}, reply interface{}, done chan *Call) *Call {
	call := &Call{
		ServiceMethod: method,
		Args:          args,
		Reply:         reply,
	}
	if done == nil {
		call.Done = make(chan *Call, 1)
	} else {
		if cap(done) < 1 {
			panic("invalid channel capacity")
		}
		call.Done = done
	}
	str, err := json.MarshalString(request{
		Method: method,
		ID:     c.nextID,
		Params: [1]interface{}{args},
	})
	if err == nil {
		err = c.ws.Send(str)
	}
	if err != nil {
		call.Error = err
		call.Done <- call
		return call
	}
	c.reqs[c.nextID] = func(rm json.RawMessage, err error) {
		if err != nil {
			call.Error = err
		} else if err = json.Unmarshal(rm, reply); err != nil {
			call.Error = err
		}
		call.Done <- call
	}
	c.nextID++
	return call
}
<commit_msg>Fixed compile-time errors<commit_after>package rpc

import (
	"github.com/MJKWoolnough/gopherjs/json"
	"github.com/gopherjs/gopherjs/js"
	"github.com/gopherjs/websocket"
)

type Call struct {
	ServiceMethod string
	Args, Reply   interface{}
	Error         error
	Done          chan *Call
}

type request struct {
	Method string         `json:"method"`
	ID     uint           `json:"id"`
	Params [1]interface{} `json:"params"`
}

type Client struct {
	ws     *websocket.WebSocket
	nextID uint
	reqs   map[uint]func(json.RawMessage, error)
}

func Dial(addr string) (*Client, error) {
	w, err := websocket.New(addr)
	if err != nil {
		return nil, err
	}
	reqs := make(map[uint]func(json.RawMessage, error))
	w.AddEventListener("message", false, func(e *js.Object) {
		var (
			r request
			m json.RawMessage
		)
		r.Params[0] = &m
		err := json.UnmarshalString(e.Get("data").String(), &r)
		f, ok := reqs[r.ID]
		if ok {
			delete(reqs, r.ID)
			f(m, err)
		}
	})
	return &Client{
		ws:   w,
		reqs: reqs,
	}, nil
}

func (c *Client) Call(method string, args interface{}, reply interface{}) error {
	call := <-c.Go(method, args, reply, make(chan *Call, 1)).Done
	return call.Error
}

func (c *Client) Close() error {
	return c.ws.Close()
}

func (c *Client) Go(method string, args interface{}, reply interface{}, done chan *Call) *Call {
	call := &Call{
		ServiceMethod: method,
		Args:          args,
		Reply:         reply,
	}
	if done == nil {
		call.Done = make(chan *Call, 1)
	} else {
		if cap(done) < 1 {
			panic("invalid channel capacity")
		}
		call.Done = done
	}
	str, err := json.MarshalString(request{
		Method: method,
		ID:     c.nextID,
		Params: [1]interface{}{args},
	})
	if err == nil {
		err = c.ws.Send(str)
	}
	if err != nil {
		call.Error = err
		call.Done <- call
		return call
	}
	c.reqs[c.nextID] = func(rm json.RawMessage, err error) {
		if err != nil {
			call.Error = err
		} else if err = json.Unmarshal(rm, reply); err != nil {
			call.Error = err
		}
		call.Done <- call
	}
	c.nextID++
	return call
}
<|endoftext|>


<commit_before>/*
 * Copyright 2016 Frank Wessels <fwessels@xs4all.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package core

import (
	"bytes"
	"encoding/json"
	"github.com/s3git/s3git-go/internal/kv"
	"os/exec"
	"strings"
	"time"
	"errors"
)

// Type to create a commit object
// - total size of json object is always a multiple of 64, so we are padding the object
// - structured as a json object
//   - json keys are in fix order
//     - in case a key contains an array, the values are sorted alphabetically

type commitObject struct {
	coreObject
	S3gitMessage        string   `json:"s3gitMessage"`        // Message describing the commit (optional)
	S3gitCommitterName  string   `json:"s3gitCommitterName"`  // Name of person doing the commit (from git)
	S3gitCommitterEmail string   `json:"s3gitCommitterEmail"` // Email of person doing the commit (from git)
	S3gitBranch         string   `json:"s3gitBranch"`         // Name of the branch
	S3gitTree           string   `json:"s3gitTree"`           // Tree object for the commit
	S3gitWarmParents    []string `json:"s3gitWarmParents"`    // List of parent commits up the (possibly split) chain
	S3gitColdParents    []string `json:"s3gitColdParents"`    // Parent commits that are no longer part of the chain
	S3gitTimeStamp      string   `json:"s3gitTimeStamp"`
	S3gitPadding        string   `json:"s3gitPadding"`
}

func makeCommitObject(message, branch, tree string, warmParents, coldParents []string, name, email string) *commitObject {

	co := commitObject{coreObject: coreObject{S3gitVersion: 1, S3gitType: kv.COMMIT}, S3gitMessage: message, S3gitBranch: branch,
		S3gitTree: tree, S3gitWarmParents: warmParents, S3gitColdParents: coldParents}

	co.S3gitCommitterName = name
	co.S3gitCommitterEmail = email
	co.S3gitTimeStamp = time.Now().Format(time.RFC3339)
	return &co
}

func (co *commitObject) ParseTime() (time.Time, error) {
	return time.Parse(time.RFC3339, co.S3gitTimeStamp)
}

func (co *commitObject) MarkWarmAndColdParents() error {

	// Mark warm and cold parents as parents in KV
	for _, parentCommit := range co.S3gitWarmParents {
		err := kv.MarkCommitAsParent(parentCommit)
		if err != nil {
			return err
		}
	}
	for _, parentCommit := range co.S3gitColdParents {
		err := kv.MarkCommitAsParent(parentCommit)
		if err != nil {
			return err
		}
	}

	return nil
}

// Return commit object based on hash
func GetCommitObject(hash string) (*commitObject, error) {

	s, err := readBlob(hash)
	if err != nil {
		return nil, err
	}

	return GetCommitObjectFromString(s)
}

// Get commit object from string contents
func GetCommitObjectFromString(s string) (*commitObject, error) {

	dec := json.NewDecoder(strings.NewReader(s))
	var co commitObject
	if err := dec.Decode(&co); err != nil {
		return nil, err
	}

	return &co, nil
}

func StoreCommitObject(message, branch string, warmParents, coldParents []string, added <-chan []byte, removed []string) (hash string, empty bool, err error) {

	// Create a tree object for this commit
	treeObject := makeTreeObject(added, removed)
	if treeObject.empty() {
		return "", true, nil
	}
	// Store tree object on disk
	treeHash, err := treeObject.writeToDisk()
	if err != nil {
		return "", false, err
	}

	name, email, err := getGitUserNameAndEmail()
	if err != nil {
		return "", false, err
	}

	// Create commit object
	commitObject := makeCommitObject(message, branch, treeHash, warmParents, coldParents, name, email)

	buf := new(bytes.Buffer)

	encoder := json.NewEncoder(buf)
	if err := encoder.Encode(commitObject); err != nil {
		return "", false, err
	}

	// Write to disk
	h, e := commitObject.write(buf, kv.COMMIT)

	err = commitObject.MarkWarmAndColdParents()
	if err != nil {
		return "", false, err
	}

	return h, false, e
}

func getGitUserNameAndEmail() (name, email string, err error) {

	_, err = exec.Command("git", "help").Output()
	if err != nil {
		return "", "", errors.New("git executable not found, is git installed? Needed for name and email configuration")
	}

	n, err := exec.Command("git", "config", "user.name").Output()
	if err != nil {
		return "", "", errors.New(`Git user.name not set. Please run 'git config --global user.name "Your Name"'`)
	}

	e, err := exec.Command("git", "config", "user.email").Output()
	if err != nil {
		return "", "", errors.New(`Git user.email not set. Please run 'git config --global user.email yourname@example.com'`)
	}

	return strings.TrimSpace(string(n)), strings.TrimSpace(string(e)), nil
}
<commit_msg>Add reference to snapshot to commit object<commit_after>/*
 * Copyright 2016 Frank Wessels <fwessels@xs4all.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package core

import (
	"bytes"
	"encoding/json"
	"github.com/s3git/s3git-go/internal/kv"
	"os/exec"
	"strings"
	"time"
	"errors"
)

// Type to create a commit object
// - total size of json object is always a multiple of 64, so we are padding the object
// - structured as a json object
//   - json keys are in fix order
//     - in case a key contains an array, the values are sorted alphabetically

type commitObject struct {
	coreObject
	S3gitMessage        string   `json:"s3gitMessage"`        // Message describing the commit (optional)
	S3gitCommitterName  string   `json:"s3gitCommitterName"`  // Name of person doing the commit (from git)
	S3gitCommitterEmail string   `json:"s3gitCommitterEmail"` // Email of person doing the commit (from git)
	S3gitBranch         string   `json:"s3gitBranch"`         // Name of the branch
	S3gitTree           string   `json:"s3gitTree"`           // Tree object for the commit
	S3gitSnapshot       string   `json:"s3gitSnapshot"`       // Snapshot object for the commit (can be empty)
	S3gitWarmParents    []string `json:"s3gitWarmParents"`    // List of parent commits up the (possibly split) chain
	S3gitColdParents    []string `json:"s3gitColdParents"`    // Parent commits that are no longer part of the chain
	S3gitTimeStamp      string   `json:"s3gitTimeStamp"`
	S3gitPadding        string   `json:"s3gitPadding"`
}

func makeCommitObject(message, branch, snapshot, tree string, warmParents, coldParents []string, name, email string) *commitObject {

	co := commitObject{coreObject: coreObject{S3gitVersion: 1, S3gitType: kv.COMMIT}, S3gitMessage: message, S3gitBranch: branch,
		S3gitTree: tree, S3gitSnapshot: snapshot, S3gitWarmParents: warmParents, S3gitColdParents: coldParents}

	co.S3gitCommitterName = name
	co.S3gitCommitterEmail = email
	co.S3gitTimeStamp = time.Now().Format(time.RFC3339)
	return &co
}

func (co *commitObject) ParseTime() (time.Time, error) {
	return time.Parse(time.RFC3339, co.S3gitTimeStamp)
}

func (co *commitObject) MarkWarmAndColdParents() error {

	// Mark warm and cold parents as parents in KV
	for _, parentCommit := range co.S3gitWarmParents {
		err := kv.MarkCommitAsParent(parentCommit)
		if err != nil {
			return err
		}
	}
	for _, parentCommit := range co.S3gitColdParents {
		err := kv.MarkCommitAsParent(parentCommit)
		if err != nil {
			return err
		}
	}

	return nil
}

// Return commit object based on hash
func GetCommitObject(hash string) (*commitObject, error) {

	s, err := readBlob(hash)
	if err != nil {
		return nil, err
	}

	return GetCommitObjectFromString(s)
}

// Get commit object from string contents
func GetCommitObjectFromString(s string) (*commitObject, error) {

	dec := json.NewDecoder(strings.NewReader(s))
	var co commitObject
	if err := dec.Decode(&co); err != nil {
		return nil, err
	}

	return &co, nil
}

func StoreCommitObject(message, branch, snapshot string, warmParents, coldParents []string, added <-chan []byte, removed []string) (hash string, empty bool, err error) {

	// Create a tree object for this commit
	treeObject := makeTreeObject(added, removed)
	if treeObject.empty() {
		return "", true, nil
	}
	// Store tree object on disk
	treeHash, err := treeObject.writeToDisk()
	if err != nil {
		return "", false, err
	}

	name, email, err := getGitUserNameAndEmail()
	if err != nil {
		return "", false, err
	}

	// Create commit object
	commitObject := makeCommitObject(message, branch, snapshot, treeHash, warmParents, coldParents, name, email)

	buf := new(bytes.Buffer)

	encoder := json.NewEncoder(buf)
	if err := encoder.Encode(commitObject); err != nil {
		return "", false, err
	}

	// Write to disk
	h, e := commitObject.write(buf, kv.COMMIT)

	err = commitObject.MarkWarmAndColdParents()
	if err != nil {
		return "", false, err
	}

	return h, false, e
}

func getGitUserNameAndEmail() (name, email string, err error) {

	_, err = exec.Command("git", "help").Output()
	if err != nil {
		return "", "", errors.New("git executable not found, is git installed? Needed for name and email configuration")
	}

	n, err := exec.Command("git", "config", "user.name").Output()
	if err != nil {
		return "", "", errors.New(`Git user.name not set. Please run 'git config --global user.name "Your Name"'`)
	}

	e, err := exec.Command("git", "config", "user.email").Output()
	if err != nil {
		return "", "", errors.New(`Git user.email not set. Please run 'git config --global user.email yourname@example.com'`)
	}

	return strings.TrimSpace(string(n)), strings.TrimSpace(string(e)), nil
}
<|endoftext|>


<commit_before>package ice

import (
	"testing"
)

func TestTimeConsuming(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}
}

// func ExampleNew() {
// m := New("a", "a", "b")
// var list []string
// for elem := range m.Iter() {
// 	list = append(list, elem.(string))
// }
// sort.Strings(list)
// fmt.Println(list)
// Output:
// [a a b]
// }
<commit_msg>Regression Test for getBestPair hang<commit_after>package ice

import (
	"testing"
	"time"

	"github.com/pions/transport/test"
)

func TestPairSearch(t *testing.T) {
	// Limit runtime in case of deadlocks
	lim := test.TimeOut(time.Second * 10)
	defer lim.Stop()

	var config AgentConfig
	a, err := NewAgent(&config)

	if err != nil {
		t.Fatalf("Error constructing ice.Agent")
	}

	if len(a.validPairs) != 0 {
		t.Fatalf("TestPairSearch is only a valid test if a.validPairs is empty on construction")
	}

	cp, err := a.getBestPair()

	if cp != nil {
		t.Fatalf("No Candidate pairs should exist")
	}

	if err == nil {
		t.Fatalf("An error should have been reported (with no available candidate pairs)")
	}

	err = a.Close()

	if err != nil {
		t.Fatalf("Close agent emits error %v", err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// gcsfs implements io/fs for GCS, adding writability.
package gcsfs

import (
	"context"
	"fmt"
	"io"
	"io/fs"
	"net/url"
	"path"
	"strings"
	"time"

	"cloud.google.com/go/storage"
	"google.golang.org/api/iterator"
)

// FromURL creates a new FS from a file:// or gs:// URL.
// client is only used for gs:// URLs and can be nil otherwise.
func FromURL(ctx context.Context, client *storage.Client, base string) (fs.FS, error) {
	u, err := url.Parse(base)
	if err != nil {
		return nil, err
	}
	switch u.Scheme {
	case "gs":
		if u.Host == "" {
			return nil, fmt.Errorf("missing bucket in %q", base)
		}
		fsys := NewFS(ctx, client, u.Host)
		if prefix := strings.TrimPrefix(u.Path, "/"); prefix != "" {
			return fs.Sub(fsys, prefix)
		}
		return fsys, nil
	case "file":
		return DirFS(u.Path), nil
	default:
		return nil, fmt.Errorf("unsupported scheme %q", u.Scheme)
	}
}

// Create creates a new file on fsys, which must be a CreateFS.
func Create(fsys fs.FS, name string) (WriteFile, error) {
	cfs, ok := fsys.(CreateFS)
	if !ok {
		return nil, &fs.PathError{Op: "create", Path: name, Err: fmt.Errorf("not implemented on type %T", fsys)}
	}
	return cfs.Create(name)
}

// CreateFS is an fs.FS that supports creating writable files.
type CreateFS interface {
	fs.FS
	Create(string) (WriteFile, error)
}

// WriteFile is an fs.File that can be written to.
// The behavior of writing and reading the same file is undefined.
type WriteFile interface {
	fs.File
	io.Writer
}

// gcsFS implements fs.FS for GCS.
type gcsFS struct {
	ctx    context.Context
	client *storage.Client
	bucket *storage.BucketHandle
	prefix string
}

var _ = fs.FS((*gcsFS)(nil))
var _ = CreateFS((*gcsFS)(nil))
var _ = fs.SubFS((*gcsFS)(nil))

// NewFS creates a new fs.FS that uses ctx for all of its operations.
// Creating a new FS does not access the network, so they can be created
// and destroyed per-context.
//
// Once the context has finished, all objects created by this FS should
// be considered invalid. In particular, Writers and Readers will be canceled.
func NewFS(ctx context.Context, client *storage.Client, bucket string) fs.FS {
	return &gcsFS{
		ctx:    ctx,
		client: client,
		bucket: client.Bucket(bucket),
	}
}

func (fsys *gcsFS) object(name string) *storage.ObjectHandle {
	return fsys.bucket.Object(path.Join(fsys.prefix, name))
}

// Open opens the named file.
func (fsys *gcsFS) Open(name string) (fs.File, error) {
	if !validPath(name) {
		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
	}
	if name == "." {
		name = ""
	}
	return &GCSFile{
		fs:   fsys,
		name: strings.TrimSuffix(name, "/"),
	}, nil
}

// Create creates the named file.
func (fsys *gcsFS) Create(name string) (WriteFile, error) {
	f, err := fsys.Open(name)
	if err != nil {
		return nil, err
	}
	return f.(*GCSFile), nil
}

func (fsys *gcsFS) Sub(dir string) (fs.FS, error) {
	copy := *fsys
	copy.prefix = path.Join(fsys.prefix, dir)
	return &copy, nil
}

// fstest likes to send us backslashes. Treat them as invalid.
func validPath(name string) bool {
	return fs.ValidPath(name) && !strings.ContainsRune(name, '\\')
}

// GCSFile implements fs.File for GCS. It is also a WriteFile.
type GCSFile struct {
	fs   *gcsFS
	name string

	reader   io.ReadCloser
	writer   io.WriteCloser
	iterator *storage.ObjectIterator
}

var _ = fs.File((*GCSFile)(nil))
var _ = fs.ReadDirFile((*GCSFile)(nil))
var _ = io.WriteCloser((*GCSFile)(nil))

func (f *GCSFile) Close() error {
	if f.reader != nil {
		defer f.reader.Close()
	}
	if f.writer != nil {
		defer f.writer.Close()
	}

	if f.reader != nil {
		err := f.reader.Close()
		if err != nil {
			return f.translateError("close", err)
		}
	}
	if f.writer != nil {
		err := f.writer.Close()
		if err != nil {
			return f.translateError("close", err)
		}
	}
	return nil
}

func (f *GCSFile) Read(b []byte) (int, error) {
	if f.reader == nil {
		var err error
		f.reader, err = f.fs.object(f.name).NewReader(f.fs.ctx)
		if err != nil {
			return 0, f.translateError("read", err)
		}
	}
	n, err := f.reader.Read(b)
	return n, f.translateError("read", err)
}

// Write writes to the GCS object associated with this File.
//
// A new object will be created unless an object with this name already exists.
// Otherwise any previous object with the same name will be replaced.
// The object will not be available (and any previous object will remain)
// until Close has been called.
func (f *GCSFile) Write(b []byte) (int, error) {
	if f.writer == nil {
		f.writer = f.fs.object(f.name).NewWriter(f.fs.ctx)
	}
	return f.writer.Write(b)
}

// ReadDir implements io/fs.ReadDirFile.
func (f *GCSFile) ReadDir(n int) ([]fs.DirEntry, error) {
	if f.iterator == nil {
		f.iterator = f.fs.iterator(f.name)
	}
	var result []fs.DirEntry
	var err error
	for {
		var info *storage.ObjectAttrs
		info, err = f.iterator.Next()
		if err != nil {
			break
		}
		result = append(result, &gcsFileInfo{info})
		if len(result) == n {
			break
		}
	}
	if err == iterator.Done {
		if n <= 0 {
			err = nil
		} else {
			err = io.EOF
		}
	}
	return result, f.translateError("readdir", err)
}

// Stats the file.
// The returned FileInfo exposes *storage.ObjectAttrs as its Sys() result.
func (f *GCSFile) Stat() (fs.FileInfo, error) {
	// Check for a real file.
	attrs, err := f.fs.object(f.name).Attrs(f.fs.ctx)
	if err != nil && err != storage.ErrObjectNotExist {
		return nil, f.translateError("stat", err)
	}
	if err == nil {
		return &gcsFileInfo{attrs: attrs}, nil
	}
	// Check for a "directory".
	iter := f.fs.iterator(f.name)
	if _, err := iter.Next(); err == nil {
		return &gcsFileInfo{
			attrs: &storage.ObjectAttrs{
				Prefix: f.name + "/",
			},
		}, nil
	}
	return nil, f.translateError("stat", storage.ErrObjectNotExist)
}

func (f *GCSFile) translateError(op string, err error) error {
	if err == nil || err == io.EOF {
		return err
	}
	nested := err
	if err == storage.ErrBucketNotExist || err == storage.ErrObjectNotExist {
		nested = fs.ErrNotExist
	} else if pe, ok := err.(*fs.PathError); ok {
		nested = pe.Err
	}
	return &fs.PathError{Op: op, Path: strings.TrimPrefix(f.name, f.fs.prefix), Err: nested}
}

// gcsFileInfo implements fs.FileInfo and fs.DirEntry.
type gcsFileInfo struct {
	attrs *storage.ObjectAttrs
}

var _ = fs.FileInfo((*gcsFileInfo)(nil))
var _ = fs.DirEntry((*gcsFileInfo)(nil))

func (fi *gcsFileInfo) Name() string {
	if fi.attrs.Prefix != "" {
		return path.Base(fi.attrs.Prefix)
	}
	return path.Base(fi.attrs.Name)
}

func (fi *gcsFileInfo) Size() int64 {
	return fi.attrs.Size
}

func (fi *gcsFileInfo) Mode() fs.FileMode {
	if fi.IsDir() {
		return fs.ModeDir | 0777
	}
	return 0666 // check fi.attrs.ACL?
}

func (fi *gcsFileInfo) ModTime() time.Time {
	return fi.attrs.Updated
}

func (fi *gcsFileInfo) IsDir() bool {
	return fi.attrs.Prefix != ""
}

func (fi *gcsFileInfo) Sys() interface{} {
	return fi.attrs
}

func (fi *gcsFileInfo) Info() (fs.FileInfo, error) {
	return fi, nil
}

func (fi *gcsFileInfo) Type() fs.FileMode {
	return fi.Mode() & fs.ModeType
}

func (fsys *gcsFS) iterator(name string) *storage.ObjectIterator {
	prefix := path.Join(fsys.prefix, name)
	if prefix != "" {
		prefix += "/"
	}
	return fsys.bucket.Objects(fsys.ctx, &storage.Query{
		Delimiter: "/",
		Prefix:    prefix,
	})
}
<commit_msg>internal/gcsfs: leave reader as true nil in case of error<commit_after>// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// gcsfs implements io/fs for GCS, adding writability.
package gcsfs

import (
	"context"
	"fmt"
	"io"
	"io/fs"
	"net/url"
	"path"
	"strings"
	"time"

	"cloud.google.com/go/storage"
	"google.golang.org/api/iterator"
)

// FromURL creates a new FS from a file:// or gs:// URL.
// client is only used for gs:// URLs and can be nil otherwise.
func FromURL(ctx context.Context, client *storage.Client, base string) (fs.FS, error) {
	u, err := url.Parse(base)
	if err != nil {
		return nil, err
	}
	switch u.Scheme {
	case "gs":
		if u.Host == "" {
			return nil, fmt.Errorf("missing bucket in %q", base)
		}
		fsys := NewFS(ctx, client, u.Host)
		if prefix := strings.TrimPrefix(u.Path, "/"); prefix != "" {
			return fs.Sub(fsys, prefix)
		}
		return fsys, nil
	case "file":
		return DirFS(u.Path), nil
	default:
		return nil, fmt.Errorf("unsupported scheme %q", u.Scheme)
	}
}

// Create creates a new file on fsys, which must be a CreateFS.
func Create(fsys fs.FS, name string) (WriteFile, error) {
	cfs, ok := fsys.(CreateFS)
	if !ok {
		return nil, &fs.PathError{Op: "create", Path: name, Err: fmt.Errorf("not implemented on type %T", fsys)}
	}
	return cfs.Create(name)
}

// CreateFS is an fs.FS that supports creating writable files.
type CreateFS interface {
	fs.FS
	Create(string) (WriteFile, error)
}

// WriteFile is an fs.File that can be written to.
// The behavior of writing and reading the same file is undefined.
type WriteFile interface {
	fs.File
	io.Writer
}

// gcsFS implements fs.FS for GCS.
type gcsFS struct {
	ctx    context.Context
	client *storage.Client
	bucket *storage.BucketHandle
	prefix string
}

var _ = fs.FS((*gcsFS)(nil))
var _ = CreateFS((*gcsFS)(nil))
var _ = fs.SubFS((*gcsFS)(nil))

// NewFS creates a new fs.FS that uses ctx for all of its operations.
// Creating a new FS does not access the network, so they can be created
// and destroyed per-context.
//
// Once the context has finished, all objects created by this FS should
// be considered invalid. In particular, Writers and Readers will be canceled.
func NewFS(ctx context.Context, client *storage.Client, bucket string) fs.FS {
	return &gcsFS{
		ctx:    ctx,
		client: client,
		bucket: client.Bucket(bucket),
	}
}

func (fsys *gcsFS) object(name string) *storage.ObjectHandle {
	return fsys.bucket.Object(path.Join(fsys.prefix, name))
}

// Open opens the named file.
func (fsys *gcsFS) Open(name string) (fs.File, error) {
	if !validPath(name) {
		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
	}
	if name == "." {
		name = ""
	}
	return &GCSFile{
		fs:   fsys,
		name: strings.TrimSuffix(name, "/"),
	}, nil
}

// Create creates the named file.
func (fsys *gcsFS) Create(name string) (WriteFile, error) {
	f, err := fsys.Open(name)
	if err != nil {
		return nil, err
	}
	return f.(*GCSFile), nil
}

func (fsys *gcsFS) Sub(dir string) (fs.FS, error) {
	copy := *fsys
	copy.prefix = path.Join(fsys.prefix, dir)
	return &copy, nil
}

// fstest likes to send us backslashes. Treat them as invalid.
func validPath(name string) bool {
	return fs.ValidPath(name) && !strings.ContainsRune(name, '\\')
}

// GCSFile implements fs.File for GCS. It is also a WriteFile.
type GCSFile struct {
	fs   *gcsFS
	name string

	reader   io.ReadCloser
	writer   io.WriteCloser
	iterator *storage.ObjectIterator
}

var _ = fs.File((*GCSFile)(nil))
var _ = fs.ReadDirFile((*GCSFile)(nil))
var _ = io.WriteCloser((*GCSFile)(nil))

func (f *GCSFile) Close() error {
	if f.reader != nil {
		defer f.reader.Close()
	}
	if f.writer != nil {
		defer f.writer.Close()
	}

	if f.reader != nil {
		err := f.reader.Close()
		if err != nil {
			return f.translateError("close", err)
		}
	}
	if f.writer != nil {
		err := f.writer.Close()
		if err != nil {
			return f.translateError("close", err)
		}
	}
	return nil
}

func (f *GCSFile) Read(b []byte) (int, error) {
	if f.reader == nil {
		reader, err := f.fs.object(f.name).NewReader(f.fs.ctx)
		if err != nil {
			return 0, f.translateError("read", err)
		}
		f.reader = reader
	}
	n, err := f.reader.Read(b)
	return n, f.translateError("read", err)
}

// Write writes to the GCS object associated with this File.
//
// A new object will be created unless an object with this name already exists.
// Otherwise any previous object with the same name will be replaced.
// The object will not be available (and any previous object will remain)
// until Close has been called.
func (f *GCSFile) Write(b []byte) (int, error) {
	if f.writer == nil {
		f.writer = f.fs.object(f.name).NewWriter(f.fs.ctx)
	}
	return f.writer.Write(b)
}

// ReadDir implements io/fs.ReadDirFile.
func (f *GCSFile) ReadDir(n int) ([]fs.DirEntry, error) {
	if f.iterator == nil {
		f.iterator = f.fs.iterator(f.name)
	}
	var result []fs.DirEntry
	var err error
	for {
		var info *storage.ObjectAttrs
		info, err = f.iterator.Next()
		if err != nil {
			break
		}
		result = append(result, &gcsFileInfo{info})
		if len(result) == n {
			break
		}
	}
	if err == iterator.Done {
		if n <= 0 {
			err = nil
		} else {
			err = io.EOF
		}
	}
	return result, f.translateError("readdir", err)
}

// Stats the file.
// The returned FileInfo exposes *storage.ObjectAttrs as its Sys() result.
func (f *GCSFile) Stat() (fs.FileInfo, error) {
	// Check for a real file.
	attrs, err := f.fs.object(f.name).Attrs(f.fs.ctx)
	if err != nil && err != storage.ErrObjectNotExist {
		return nil, f.translateError("stat", err)
	}
	if err == nil {
		return &gcsFileInfo{attrs: attrs}, nil
	}
	// Check for a "directory".
	iter := f.fs.iterator(f.name)
	if _, err := iter.Next(); err == nil {
		return &gcsFileInfo{
			attrs: &storage.ObjectAttrs{
				Prefix: f.name + "/",
			},
		}, nil
	}
	return nil, f.translateError("stat", storage.ErrObjectNotExist)
}

func (f *GCSFile) translateError(op string, err error) error {
	if err == nil || err == io.EOF {
		return err
	}
	nested := err
	if err == storage.ErrBucketNotExist || err == storage.ErrObjectNotExist {
		nested = fs.ErrNotExist
	} else if pe, ok := err.(*fs.PathError); ok {
		nested = pe.Err
	}
	return &fs.PathError{Op: op, Path: strings.TrimPrefix(f.name, f.fs.prefix), Err: nested}
}

// gcsFileInfo implements fs.FileInfo and fs.DirEntry.
type gcsFileInfo struct {
	attrs *storage.ObjectAttrs
}

var _ = fs.FileInfo((*gcsFileInfo)(nil))
var _ = fs.DirEntry((*gcsFileInfo)(nil))

func (fi *gcsFileInfo) Name() string {
	if fi.attrs.Prefix != "" {
		return path.Base(fi.attrs.Prefix)
	}
	return path.Base(fi.attrs.Name)
}

func (fi *gcsFileInfo) Size() int64 {
	return fi.attrs.Size
}

func (fi *gcsFileInfo) Mode() fs.FileMode {
	if fi.IsDir() {
		return fs.ModeDir | 0777
	}
	return 0666 // check fi.attrs.ACL?
}

func (fi *gcsFileInfo) ModTime() time.Time {
	return fi.attrs.Updated
}

func (fi *gcsFileInfo) IsDir() bool {
	return fi.attrs.Prefix != ""
}

func (fi *gcsFileInfo) Sys() interface{} {
	return fi.attrs
}

func (fi *gcsFileInfo) Info() (fs.FileInfo, error) {
	return fi, nil
}

func (fi *gcsFileInfo) Type() fs.FileMode {
	return fi.Mode() & fs.ModeType
}

func (fsys *gcsFS) iterator(name string) *storage.ObjectIterator {
	prefix := path.Join(fsys.prefix, name)
	if prefix != "" {
		prefix += "/"
	}
	return fsys.bucket.Objects(fsys.ctx, &storage.Query{
		Delimiter: "/",
		Prefix:    prefix,
	})
}
<|endoftext|>


<commit_before>// Copyright © 2014 Jay R. Wren <jrwren@xmtp.net>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/BurntSushi/toml"
	"github.com/pearkes/dnsimple"
)

var verbose = flag.Bool("v", false, "Use verbose output")
var list = flag.Bool("l", false, "List domains.")
var update = flag.String("u", "", "Update or create record. The format is 'domain name type oldvalue newvlaue ttl'. Use - for oldvalue to create a new record.")
var del = flag.String("d", "", "Delete record. The format is 'domain name type value'")

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
		flag.PrintDefaults()
		fmt.Fprintln(os.Stderr, ".domasimurc config file example:")
		toml.NewEncoder(os.Stderr).Encode(Config{"you@example.com", "TOKENHERE1234"})
	}
	flag.Parse()
	user, token, err := getCreds()
	if err != nil {
		fmt.Fprintln(os.Stderr, "could not read config", err)
		return
	}
	client, err := dnsimple.NewClient(user, token)
	if err != nil {
		fmt.Fprintln(os.Stderr, "could not connect to dnsimple", err)
		return
	}
	if *list {
		domains, err := client.GetDomains()
		if err != nil {
			fmt.Fprintln(os.Stderr, "could get domains %v", err)
			return
		}
		for _, domain := range domains {
			if *verbose {
				fmt.Println(domain.Name, domain.ExpiresOn)
			} else {
				fmt.Println(domain.Name)
			}
		}
		return
	}
	if *update != "" {
		id, err := createOrUpdate(client, *update)
		if err != nil {
			fmt.Fprintln(os.Stderr, "could not get create or update:", err)
		} else {
			fmt.Printf("record written with id %s\n", id)
		}
		return
	}
	if *del != "" {
		id, err := deleteRecord(client, *del)
		if err != nil {
			fmt.Fprintln(os.Stderr, "could not delete:", err)
		} else {
			fmt.Printf("record deleted with id %s\n", id)
		}
		return
	}
	for _, domain := range flag.Args() {
		records, err := client.GetRecords(domain)
		if err != nil {
			fmt.Fprintln(os.Stderr, "could not get records:", err)
			continue
		}
		for _, record := range records {
			if *verbose {
				fmt.Println(record.Name, record.RecordType, record.Content, record.Ttl, record.Prio)
			} else {
				fmt.Println(record.Name, record.RecordType, record.Content)
			}
		}
	}
}

func getCreds() (string, string, error) {
	configFileName := os.Getenv("DOMASIMU_CONF")
	if configFileName == "" {
		configFileName = filepath.Join(os.Getenv("HOME"), ".domasimurc")
	}
	var config Config
	_, err := toml.DecodeFile(configFileName, &config)
	if err != nil {
		return "", "", err
	}
	return config.User, config.Token, nil
}

type Config struct {
	User  string
	Token string
}

func createOrUpdate(client *dnsimple.Client, message string) (string, error) {
	pieces := strings.Split(message, " ")
	if len(pieces) != 6 {
		return "", fmt.Errorf("expected space seperated domain, name, type, oldvalue, newvalue, ttl")
	}
	domain := pieces[0]
	var changeRecord dnsimple.ChangeRecord
	changeRecord.Name = pieces[1]
	changeRecord.Type = pieces[2]
	changeRecord.Value = pieces[3]
	newRecord := changeRecord
	newRecord.Value = pieces[4]
	newRecord.Ttl = pieces[5]
	id, err := getRecordIdByValue(client, domain, &changeRecord)
	if err != nil {
		return "", err
	}
	var respId string
	if id == "" {
		respId, err = client.CreateRecord(domain, &newRecord)
	} else {
		respId, err = client.UpdateRecord(domain, id, &newRecord)
	}
	if err != nil {
		return "", err
	}
	return respId, nil
}

func deleteRecord(client *dnsimple.Client, message string) (string, error) {
	pieces := strings.Split(message, " ")
	if len(pieces) != 4 {
		return "", fmt.Errorf("expected space seperated domain, name, type, value")
	}
	domain := pieces[0]
	var changeRecord dnsimple.ChangeRecord
	changeRecord.Name = pieces[1]
	changeRecord.Type = pieces[2]
	changeRecord.Value = pieces[3]
	id, err := getRecordIdByValue(client, domain, &changeRecord)
	if err != nil {
		return "", err
	}
	if id == "" {
		return "", fmt.Errorf("could not find record")
	}
	err = client.DestroyRecord(domain, id)
	return id, err
}

func getRecordIdByValue(client *dnsimple.Client, domain string, changeRecord *dnsimple.ChangeRecord) (string, error) {
	records, err := client.GetRecords(domain)
	if err != nil {
		return "", err
	}
	var id string
	for _, record := range records {
		if record.Name == changeRecord.Name && record.RecordType == changeRecord.Type && record.Content == changeRecord.Value {
			id = record.StringId()
			break
		}
	}
	return id, nil
}
<commit_msg>Upgrade client for support APIv2<commit_after>// Copyright © 2014 Jay R. Wren <jrwren@xmtp.net>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/BurntSushi/toml"
	"github.com/dnsimple/dnsimple-go/dnsimple"
	"golang.org/x/oauth2"
)

var verbose = flag.Bool("v", false, "Use verbose output")
var list = flag.Bool("l", false, "List domains.")
var update = flag.String("u", "", "Update or create record. The format is 'domain name type oldvalue newvlaue ttl'. Use - for oldvalue to create a new record.")
var del = flag.String("d", "", "Delete record. The format is 'domain name type value'")

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
		flag.PrintDefaults()
		fmt.Fprintln(os.Stderr, ".domasimurc config file example:")
		toml.NewEncoder(os.Stderr).Encode(Config{"you@example.com", "TOKENHERE1234"})
	}
	flag.Parse()
	_, token, err := getCreds()
	if err != nil {
		fmt.Fprintln(os.Stderr, "could not read config", err)
		return
	}

	ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
	tc := oauth2.NewClient(context.Background(), ts)
	client := dnsimple.NewClient(tc)

	whoamiResponse, err := client.Identity.Whoami()
	if err != nil {
		fmt.Fprintln(os.Stderr, "could not connect to dnsimple", err)
		return
	}
	accountID := strconv.FormatInt(whoamiResponse.Data.Account.ID, 10)

	if *list {
		domainsResponse, err := client.Domains.ListDomains(accountID, nil)
		if err != nil {
			fmt.Fprintf(os.Stderr, "could get domains %v\n", err)
			return
		}
		for _, domain := range domainsResponse.Data {
			if *verbose {
				fmt.Println(domain.Name, domain.ExpiresOn)
			} else {
				fmt.Println(domain.Name)
			}
		}
		return
	}
	if *update != "" {
		id, err := createOrUpdate(client, *update, accountID)
		if err != nil {
			fmt.Fprintln(os.Stderr, "could not get create or update:", err)
		} else {
			fmt.Printf("record written with id %s\n", id)
		}
		return
	}
	if *del != "" {
		id, err := deleteRecord(client, *del, accountID)
		if err != nil {
			fmt.Fprintln(os.Stderr, "could not delete:", err)
		} else {
			fmt.Printf("record deleted with id %s\n", id)
		}
		return
	}
	for _, domain := range flag.Args() {
		listZoneRecordsResponse, err := client.Zones.ListRecords(accountID, domain, nil)
		if err != nil {
			fmt.Fprintln(os.Stderr, "could not get records:", err)
			continue
		}

		for _, record := range listZoneRecordsResponse.Data {
			if *verbose {
				fmt.Println(record.Name, record.Type, record.Content, record.TTL, record.Priority)
			} else {
				fmt.Println(record.Name, record.Type, record.Content)
			}
		}
	}
}

func getCreds() (string, string, error) {
	configFileName := os.Getenv("DOMASIMU_CONF")
	if configFileName == "" {
		configFileName = filepath.Join(os.Getenv("HOME"), ".domasimurc")
	}
	var config Config
	_, err := toml.DecodeFile(configFileName, &config)
	if err != nil {
		return "", "", err
	}
	return config.User, config.Token, nil
}

type Config struct {
	User  string
	Token string
}

func createOrUpdate(client *dnsimple.Client, message string, accountID string) (string, error) {
	pieces := strings.Split(message, " ")
	if len(pieces) != 6 {
		return "", fmt.Errorf("expected space seperated domain, name, type, oldvalue, newvalue, ttl")
	}

	domain := pieces[0]
	changeRecord := dnsimple.ZoneRecord{
		Name: pieces[1],
		Type: pieces[2],
	}
	oldValue := pieces[3]
	newRecord := changeRecord
	newRecord.Content = pieces[4]
	ttl, _ := strconv.Atoi(pieces[5])
	newRecord.TTL = ttl
	id, err := getRecordIDByValue(client, domain, oldValue, accountID, &changeRecord)

	if err != nil {
		return "", err
	}

	var respID string
	if id == 0 {
		zoneRecordResponse, err := client.Zones.CreateRecord(accountID, domain, newRecord)
		respID = strconv.FormatInt(zoneRecordResponse.Data.ID, 10)

		if err != nil {
			return "", err
		}
	} else {
		zoneRecordResponse, err := client.Zones.UpdateRecord(accountID, domain, id, newRecord)
		respID = strconv.FormatInt(zoneRecordResponse.Data.ID, 10)

		if err != nil {
			return "", err
		}
	}

	return respID, nil
}

func deleteRecord(client *dnsimple.Client, message, accountID string) (string, error) {
	pieces := strings.Split(message, " ")
	if len(pieces) != 4 {
		return "", fmt.Errorf("expected space seperated domain, name, type, value")
	}
	domain := pieces[0]
	changeRecord := dnsimple.ZoneRecord{
		Name: pieces[1],
		Type: pieces[2],
	}
	value := pieces[3]
	id, err := getRecordIDByValue(client, domain, value, accountID, &changeRecord)
	if err != nil {
		return "", err
	}
	if id == 0 {
		return "", fmt.Errorf("could not find record")
	}
	_, err = client.Zones.DeleteRecord(accountID, domain, id)
	respID := strconv.FormatInt(id, 10)

	return respID, err
}

func getRecordIDByValue(client *dnsimple.Client, domain, value, accountID string, changeRecord *dnsimple.ZoneRecord) (int64, error) {
	recordResponse, err := client.Zones.ListRecords(accountID, domain, nil)
	if err != nil {
		return 0, err
	}
	var id int64
	for _, record := range recordResponse.Data {
		if record.Name == changeRecord.Name && record.Type == changeRecord.Type && record.Content == value {
			id = record.ID
			break
		}
	}
	return id, nil
}
<|endoftext|>


<commit_before>package modules

import (
	"fmt"
	"strings"
	"time"

	"github.com/pajlada/pajbot2/pkg"
	"github.com/pajlada/pajbot2/pkg/report"
	"github.com/pajlada/pajbot2/pkg/utils"
)

type Report struct {
	botChannel pkg.BotChannel

	reportHolder *report.Holder
}

var _ pkg.Module = &Report{}

func newReport() pkg.Module {
	return &Report{
		reportHolder: _server.reportHolder,
	}
}

var reportSpec = moduleSpec{
	id:    "report",
	name:  "Report",
	maker: newReport,
}

func (m *Report) ProcessReport(bot pkg.Sender, source pkg.Channel, user pkg.User, parts []string) error {
	duration := 600

	if parts[0] == "!report" {
	} else if parts[0] == "!longreport" {
		duration = 28800
	} else {
		return nil
	}

	if !user.HasPermission(source, pkg.PermissionReport) {
		bot.Whisper(user, "you don't have permissions to use the !report command")
		return nil
	}

	var reportedUsername string
	var reason string

	reportedUsername = strings.ToLower(utils.FilterUsername(parts[1]))

	if reportedUsername == user.GetName() {
		return nil
	}

	if len(parts) >= 3 {
		reason = strings.Join(parts[2:], " ")
	}

	m.report(bot, user, source, reportedUsername, reason, duration)

	return nil
}

func (m *Report) Initialize(botChannel pkg.BotChannel, settings []byte) error {
	m.botChannel = botChannel

	return nil
}

func (m *Report) Disable() error {
	return nil
}

func (m *Report) Spec() pkg.ModuleSpec {
	return &reportSpec
}

func (m *Report) BotChannel() pkg.BotChannel {
	return m.botChannel
}

func (m *Report) OnWhisper(bot pkg.Sender, source pkg.User, message pkg.Message) error {
	const usageString = `Usage: #channel !report username (reason) i.e. #forsen !report Karl_Kons spamming stuff`

	parts := strings.Split(message.GetText(), " ")
	if len(parts) < 1 {
		return nil
	}
	channel := bot.MakeChannel(m.botChannel.ChannelName())

	m.ProcessReport(bot, channel, source, parts)
	return nil
}

func (m *Report) report(bot pkg.Sender, reporter pkg.User, targetChannel pkg.Channel, targetUsername string, reason string, duration int) {
	fmt.Sprintf("%s reported %s in #%s (%s) - https://api.gempir.com/channel/forsen/user/%s", reporter.GetName(), targetUsername, targetChannel.GetChannel(), reason, targetUsername)

	r := report.Report{
		Channel: report.ReportUser{
			ID:   targetChannel.GetID(),
			Name: targetChannel.GetChannel(),
			Type: "twitch",
		},
		Reporter: report.ReportUser{
			ID:   reporter.GetID(),
			Name: reporter.GetName(),
		},
		Target: report.ReportUser{
			ID:   bot.GetUserStore().GetID(targetUsername),
			Name: targetUsername,
		},
		Reason: reason,
		Time:   time.Now(),
	}
	r.Logs = bot.GetUserContext().GetContext(r.Channel.ID, r.Target.ID)

	oldReport, inserted, _ := m.reportHolder.Register(r)

	if !inserted {
		// Report for this user in this channel already exists

		if time.Now().Sub(oldReport.Time) < time.Minute*10 {
			// User was reported less than 10 minutes ago, don't let this user be timed out again
			fmt.Printf("Skipping timeout because user was timed out too shortly ago: %s\n", time.Now().Sub(oldReport.Time))
			return
		}

		fmt.Println("Update report")
		r.ID = oldReport.ID
		m.reportHolder.Update(r)
	}

	bot.Timeout(targetChannel, bot.MakeUser(targetUsername), duration, "")
}

func (m *Report) OnMessage(bot pkg.Sender, source pkg.Channel, user pkg.User, message pkg.Message, action pkg.Action) error {
	parts := strings.Split(message.GetText(), " ")
	if len(parts) < 2 {
		return nil
	}

	m.ProcessReport(bot, source, user, parts)
	return nil
}
<commit_msg>recomment<commit_after>package modules

import (
	"fmt"
	"strings"
	"time"

	"github.com/pajlada/pajbot2/pkg"
	"github.com/pajlada/pajbot2/pkg/report"
	"github.com/pajlada/pajbot2/pkg/utils"
)

type Report struct {
	botChannel pkg.BotChannel

	reportHolder *report.Holder
}

var _ pkg.Module = &Report{}

func newReport() pkg.Module {
	return &Report{
		reportHolder: _server.reportHolder,
	}
}

var reportSpec = moduleSpec{
	id:    "report",
	name:  "Report",
	maker: newReport,
}

func (m *Report) ProcessReport(bot pkg.Sender, source pkg.Channel, user pkg.User, parts []string) error {
	duration := 600

	if parts[0] == "!report" {
	} else if parts[0] == "!longreport" {
		duration = 28800
	} else {
		return nil
	}

	if !user.HasPermission(source, pkg.PermissionReport) {
		bot.Whisper(user, "you don't have permissions to use the !report command")
		return nil
	}

	var reportedUsername string
	var reason string

	reportedUsername = strings.ToLower(utils.FilterUsername(parts[1]))

	if reportedUsername == user.GetName() {
		return nil
	}

	if len(parts) >= 3 {
		reason = strings.Join(parts[2:], " ")
	}

	m.report(bot, user, source, reportedUsername, reason, duration)

	return nil
}

func (m *Report) Initialize(botChannel pkg.BotChannel, settings []byte) error {
	m.botChannel = botChannel

	return nil
}

func (m *Report) Disable() error {
	return nil
}

func (m *Report) Spec() pkg.ModuleSpec {
	return &reportSpec
}

func (m *Report) BotChannel() pkg.BotChannel {
	return m.botChannel
}

func (m *Report) OnWhisper(bot pkg.Sender, source pkg.User, message pkg.Message) error {
	const usageString = `Usage: #channel !report username (reason) i.e. #forsen !report Karl_Kons spamming stuff`

	parts := strings.Split(message.GetText(), " ")
	if len(parts) < 1 {
		return nil
	}
	channel := bot.MakeChannel(m.botChannel.ChannelName())

	m.ProcessReport(bot, channel, source, parts)
	return nil
}

func (m *Report) report(bot pkg.Sender, reporter pkg.User, targetChannel pkg.Channel, targetUsername string, reason string, duration int) {
	// fmt.Sprintf("%s reported %s in #%s (%s) - https://api.gempir.com/channel/forsen/user/%s", reporter.GetName(), targetUsername, targetChannel.GetChannel(), reason, targetUsername)

	r := report.Report{
		Channel: report.ReportUser{
			ID:   targetChannel.GetID(),
			Name: targetChannel.GetChannel(),
			Type: "twitch",
		},
		Reporter: report.ReportUser{
			ID:   reporter.GetID(),
			Name: reporter.GetName(),
		},
		Target: report.ReportUser{
			ID:   bot.GetUserStore().GetID(targetUsername),
			Name: targetUsername,
		},
		Reason: reason,
		Time:   time.Now(),
	}
	r.Logs = bot.GetUserContext().GetContext(r.Channel.ID, r.Target.ID)

	oldReport, inserted, _ := m.reportHolder.Register(r)

	if !inserted {
		// Report for this user in this channel already exists

		if time.Now().Sub(oldReport.Time) < time.Minute*10 {
			// User was reported less than 10 minutes ago, don't let this user be timed out again
			fmt.Printf("Skipping timeout because user was timed out too shortly ago: %s\n", time.Now().Sub(oldReport.Time))
			return
		}

		fmt.Println("Update report")
		r.ID = oldReport.ID
		m.reportHolder.Update(r)
	}

	bot.Timeout(targetChannel, bot.MakeUser(targetUsername), duration, "")
}

func (m *Report) OnMessage(bot pkg.Sender, source pkg.Channel, user pkg.User, message pkg.Message, action pkg.Action) error {
	parts := strings.Split(message.GetText(), " ")
	if len(parts) < 2 {
		return nil
	}

	m.ProcessReport(bot, source, user, parts)
	return nil
}
<|endoftext|>


<commit_before>package graphite

import (
	"fmt"
	m "github.com/grafana/grafana/pkg/models"
)

func GetSeries(job *m.AlertJob) (m.TimeSeriesSlice, error) {
	if job.Datasource.Type == m.DS_GRAPHITE {
		return GraphiteClient{}.GetSeries(job)
	}

	return nil, fmt.Errorf("Grafana does not support alerts for %s", job.Datasource.Type)
}
<commit_msg>feat(alerting): add interface for alert backend<commit_after>package graphite

import (
	"fmt"

	m "github.com/grafana/grafana/pkg/models"
)

// AlertDatasource is bacon
type AlertDatasource interface {
	GetSeries(job *m.AlertJob) (m.TimeSeriesSlice, error)
}

// GetSeries returns timeseries data from the datasource
func GetSeries(job *m.AlertJob) (m.TimeSeriesSlice, error) {
	if job.Datasource.Type == m.DS_GRAPHITE {
		return GraphiteClient{}.GetSeries(job)
	}

	return nil, fmt.Errorf("Grafana does not support alerts for %s", job.Datasource.Type)
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/exercism/cli/config"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
)

func TestDownloadWithoutToken(t *testing.T) {
	cfg := config.Configuration{
		UserViperConfig: viper.New(),
	}

	err := runDownload(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), []string{})
	if assert.Error(t, err) {
		assert.Regexp(t, "Welcome to Exercism", err.Error())
	}
}

func TestDownloadWithoutWorkspace(t *testing.T) {
	v := viper.New()
	v.Set("token", "abc123")
	cfg := config.Configuration{
		UserViperConfig: v,
	}

	err := runDownload(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), []string{})
	if assert.Error(t, err) {
		assert.Regexp(t, "re-run the configure", err.Error())
	}
}

func TestDownloadWithoutBaseURL(t *testing.T) {
	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", "/home/whatever")
	cfg := config.Configuration{
		UserViperConfig: v,
	}

	err := runDownload(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), []string{})
	if assert.Error(t, err) {
		assert.Regexp(t, "re-run the configure", err.Error())
	}
}

func TestDownloadWithoutFlags(t *testing.T) {
	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", "/home/username")
	v.Set("apibaseurl", "http://example.com")

	cfg := config.Configuration{
		UserViperConfig: v,
	}

	flags := pflag.NewFlagSet("fake", pflag.PanicOnError)
	setupDownloadFlags(flags)

	err := runDownload(cfg, flags, []string{})
	if assert.Error(t, err) {
		assert.Regexp(t, "need an --exercise name or a solution --uuid", err.Error())
	}
}

func TestDownloadTHISONE(t *testing.T) {
	oldOut := Out
	oldErr := Err
	Out = ioutil.Discard
	Err = ioutil.Discard
	defer func() {
		Out = oldOut
		Err = oldErr
	}()

	testCases := []struct {
		requestor       string
		expectedDir     string
		flag, flagValue string
	}{
		{requestorSelf, "", "exercise", "bogus-exercise"},
		{requestorSelf, "", "uuid", "bogus-id"},
		{requestorOther, filepath.Join("users", "alice"), "uuid", "bogus-id"},
	}

	for _, tc := range testCases {
		tmpDir, err := ioutil.TempDir("", "download-cmd")
		assert.NoError(t, err)

		ts := fakeDownloadServer(tc.requestor)
		defer ts.Close()

		v := viper.New()
		v.Set("workspace", tmpDir)
		v.Set("apibaseurl", ts.URL)
		v.Set("token", "abc123")

		cfg := config.Configuration{
			UserViperConfig: v,
		}
		flags := pflag.NewFlagSet("fake", pflag.PanicOnError)
		setupDownloadFlags(flags)
		flags.Set(tc.flag, tc.flagValue)

		err = runDownload(cfg, flags, []string{})
		assert.NoError(t, err)

		assertDownloadedCorrectFiles(t, filepath.Join(tmpDir, tc.expectedDir), tc.requestor)
	}
}

func fakeDownloadServer(requestor string) *httptest.Server {
	mux := http.NewServeMux()
	server := httptest.NewServer(mux)

	path1 := "file-1.txt"
	mux.HandleFunc("/"+path1, func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "this is file 1")
	})

	path2 := "subdir/file-2.txt"
	mux.HandleFunc("/"+path2, func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "this is file 2")
	})

	path3 := "file-3.txt"
	mux.HandleFunc("/"+path3, func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "")
	})

	payloadBody := fmt.Sprintf(payloadTemplate, requestor, server.URL+"/", path1, path2, path3)
	mux.HandleFunc("/solutions/latest", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, payloadBody)
	})
	mux.HandleFunc("/solutions/bogus-id", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, payloadBody)
	})

	return server
}

func assertDownloadedCorrectFiles(t *testing.T, targetDir, requestor string) {
	metadata := `{"track":"bogus-track","exercise":"bogus-exercise","id":"bogus-id","url":"","handle":"alice","is_requester":%s,"auto_approve":false}`
	expectedFiles := []struct {
		desc     string
		path     string
		contents string
	}{
		{
			desc:     "a file in the exercise root directory",
			path:     filepath.Join(targetDir, "bogus-track", "bogus-exercise", "file-1.txt"),
			contents: "this is file 1",
		},
		{
			desc:     "a file in a subdirectory",
			path:     filepath.Join(targetDir, "bogus-track", "bogus-exercise", "subdir", "file-2.txt"),
			contents: "this is file 2",
		},
		{
			desc:     "the solution metadata file",
			path:     filepath.Join(targetDir, "bogus-track", "bogus-exercise", ".solution.json"),
			contents: fmt.Sprintf(metadata, requestor),
		},
	}

	for _, file := range expectedFiles {
		t.Run(file.desc, func(t *testing.T) {
			b, err := ioutil.ReadFile(file.path)
			assert.NoError(t, err)
			assert.Equal(t, file.contents, string(b))
		})
	}

	path := filepath.Join(targetDir, "bogus-track", "bogus-exercise", "file-3.txt")
	_, err := os.Lstat(path)
	assert.True(t, os.IsNotExist(err), "It should not write the file if empty.")
}

const requestorSelf = "true"
const requestorOther = "false"

const payloadTemplate = `
{
	"solution": {
		"id": "bogus-id",
		"user": {
			"handle": "alice",
			"is_requester": %s
		},
		"exercise": {
			"id": "bogus-exercise",
			"instructions_url": "http://example.com/bogus-exercise",
			"auto_approve": false,
			"track": {
				"id": "bogus-track",
				"language": "Bogus Language"
			}
		},
		"file_download_base_url": "%s",
		"files": [
		"%s",
		"%s",
		"%s"
		],
		"iteration": {
			"submitted_at": "2017-08-21t10:11:12.130z"
		}
	}
}
`
<commit_msg>Delete stray test suffix<commit_after>package cmd

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/exercism/cli/config"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
)

func TestDownloadWithoutToken(t *testing.T) {
	cfg := config.Configuration{
		UserViperConfig: viper.New(),
	}

	err := runDownload(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), []string{})
	if assert.Error(t, err) {
		assert.Regexp(t, "Welcome to Exercism", err.Error())
	}
}

func TestDownloadWithoutWorkspace(t *testing.T) {
	v := viper.New()
	v.Set("token", "abc123")
	cfg := config.Configuration{
		UserViperConfig: v,
	}

	err := runDownload(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), []string{})
	if assert.Error(t, err) {
		assert.Regexp(t, "re-run the configure", err.Error())
	}
}

func TestDownloadWithoutBaseURL(t *testing.T) {
	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", "/home/whatever")
	cfg := config.Configuration{
		UserViperConfig: v,
	}

	err := runDownload(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), []string{})
	if assert.Error(t, err) {
		assert.Regexp(t, "re-run the configure", err.Error())
	}
}

func TestDownloadWithoutFlags(t *testing.T) {
	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", "/home/username")
	v.Set("apibaseurl", "http://example.com")

	cfg := config.Configuration{
		UserViperConfig: v,
	}

	flags := pflag.NewFlagSet("fake", pflag.PanicOnError)
	setupDownloadFlags(flags)

	err := runDownload(cfg, flags, []string{})
	if assert.Error(t, err) {
		assert.Regexp(t, "need an --exercise name or a solution --uuid", err.Error())
	}
}

func TestDownload(t *testing.T) {
	oldOut := Out
	oldErr := Err
	Out = ioutil.Discard
	Err = ioutil.Discard
	defer func() {
		Out = oldOut
		Err = oldErr
	}()

	testCases := []struct {
		requestor       string
		expectedDir     string
		flag, flagValue string
	}{
		{requestorSelf, "", "exercise", "bogus-exercise"},
		{requestorSelf, "", "uuid", "bogus-id"},
		{requestorOther, filepath.Join("users", "alice"), "uuid", "bogus-id"},
	}

	for _, tc := range testCases {
		tmpDir, err := ioutil.TempDir("", "download-cmd")
		assert.NoError(t, err)

		ts := fakeDownloadServer(tc.requestor)
		defer ts.Close()

		v := viper.New()
		v.Set("workspace", tmpDir)
		v.Set("apibaseurl", ts.URL)
		v.Set("token", "abc123")

		cfg := config.Configuration{
			UserViperConfig: v,
		}
		flags := pflag.NewFlagSet("fake", pflag.PanicOnError)
		setupDownloadFlags(flags)
		flags.Set(tc.flag, tc.flagValue)

		err = runDownload(cfg, flags, []string{})
		assert.NoError(t, err)

		assertDownloadedCorrectFiles(t, filepath.Join(tmpDir, tc.expectedDir), tc.requestor)
	}
}

func fakeDownloadServer(requestor string) *httptest.Server {
	mux := http.NewServeMux()
	server := httptest.NewServer(mux)

	path1 := "file-1.txt"
	mux.HandleFunc("/"+path1, func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "this is file 1")
	})

	path2 := "subdir/file-2.txt"
	mux.HandleFunc("/"+path2, func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "this is file 2")
	})

	path3 := "file-3.txt"
	mux.HandleFunc("/"+path3, func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "")
	})

	payloadBody := fmt.Sprintf(payloadTemplate, requestor, server.URL+"/", path1, path2, path3)
	mux.HandleFunc("/solutions/latest", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, payloadBody)
	})
	mux.HandleFunc("/solutions/bogus-id", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, payloadBody)
	})

	return server
}

func assertDownloadedCorrectFiles(t *testing.T, targetDir, requestor string) {
	metadata := `{"track":"bogus-track","exercise":"bogus-exercise","id":"bogus-id","url":"","handle":"alice","is_requester":%s,"auto_approve":false}`
	expectedFiles := []struct {
		desc     string
		path     string
		contents string
	}{
		{
			desc:     "a file in the exercise root directory",
			path:     filepath.Join(targetDir, "bogus-track", "bogus-exercise", "file-1.txt"),
			contents: "this is file 1",
		},
		{
			desc:     "a file in a subdirectory",
			path:     filepath.Join(targetDir, "bogus-track", "bogus-exercise", "subdir", "file-2.txt"),
			contents: "this is file 2",
		},
		{
			desc:     "the solution metadata file",
			path:     filepath.Join(targetDir, "bogus-track", "bogus-exercise", ".solution.json"),
			contents: fmt.Sprintf(metadata, requestor),
		},
	}

	for _, file := range expectedFiles {
		t.Run(file.desc, func(t *testing.T) {
			b, err := ioutil.ReadFile(file.path)
			assert.NoError(t, err)
			assert.Equal(t, file.contents, string(b))
		})
	}

	path := filepath.Join(targetDir, "bogus-track", "bogus-exercise", "file-3.txt")
	_, err := os.Lstat(path)
	assert.True(t, os.IsNotExist(err), "It should not write the file if empty.")
}

const requestorSelf = "true"
const requestorOther = "false"

const payloadTemplate = `
{
	"solution": {
		"id": "bogus-id",
		"user": {
			"handle": "alice",
			"is_requester": %s
		},
		"exercise": {
			"id": "bogus-exercise",
			"instructions_url": "http://example.com/bogus-exercise",
			"auto_approve": false,
			"track": {
				"id": "bogus-track",
				"language": "Bogus Language"
			}
		},
		"file_download_base_url": "%s",
		"files": [
		"%s",
		"%s",
		"%s"
		],
		"iteration": {
			"submitted_at": "2017-08-21t10:11:12.130z"
		}
	}
}
`
<|endoftext|>