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

package log

import (
	"bytes"
	"log"
	"strings"

	"gopkg.in/check.v1"
)

type FileLoggerSuite struct {
	l  Logger
	fl *fileLogger
	b  *bytes.Buffer
}

var _ = check.Suite(&FileLoggerSuite{})

func (s *FileLoggerSuite) SetUpSuite(c *check.C) {
	s.l = NewFileLogger("/dev/null", true)
	s.fl, _ = s.l.(*fileLogger)
}

func (s *FileLoggerSuite) SetUpTest(c *check.C) {
	s.b = &bytes.Buffer{}
	s.fl.logger = log.New(s.b, "", log.LstdFlags)
}

func (s *FileLoggerSuite) TestNewFileLoggerReturnsALogger(c *check.C) {
	_, ok := s.l.(Logger)
	c.Assert(ok, check.Equals, true)
}

func (s *FileLoggerSuite) TestNewWriterLogger(c *check.C) {
	var buf bytes.Buffer
	logger := newWriterLogger(&buf, true)
	logger.Errorf("something went wrong: %s", "this")
	result := strings.SplitN(buf.String(), "\n", 2)[0]
	c.Assert(result, check.Matches, `^.*ERROR: something went wrong: this$`)
}

func (s *FileLoggerSuite) TestNewFileLoggerInitializesWriter(c *check.C) {
	c.Assert(s.fl.logger, check.FitsTypeOf, &log.Logger{})
}

func (s *FileLoggerSuite) TestErrorShouldPrefixMessage(c *check.C) {
	s.l.Error("something terrible happened")
	c.Assert(s.b.String(), check.Matches, ".* ERROR: something terrible happened\n$")
}

func (s *FileLoggerSuite) TestErrorfShouldFormatErrorAndPrefixMessage(c *check.C) {
	s.l.Errorf(`this is the error: "%s"`, "something bad happened")
	c.Assert(s.b.String(), check.Matches, `.* ERROR: this is the error: "something bad happened"\n$`)
}

func (s *FileLoggerSuite) TestDebugShouldPrefixMessage(c *check.C) {
	s.l.Debug("doing some stuff here")
	c.Assert(s.b.String(), check.Matches, ".* DEBUG: doing some stuff here\n$")
}

func (s *FileLoggerSuite) TestDebugfShouldFormatAndPrefixMessage(c *check.C) {
	s.l.Debugf(`message is "%s"`, "some debug message")
	c.Assert(s.b.String(), check.Matches, `.* DEBUG: message is "some debug message"\n$`)
}

func (s *FileLoggerSuite) TestDebugShouldNotWriteDebugIsSetToFalse(c *check.C) {
	l := NewFileLogger("/dev/null", false)
	fl, _ := l.(*fileLogger)
	b := &bytes.Buffer{}
	fl.logger = log.New(b, "", log.LstdFlags)
	l.Debug("sould not log this")
	c.Assert(b.String(), check.Equals, "")
	l.Debugf("sould not log this either %d", 1)
	c.Assert(b.String(), check.Equals, "")
}

func (s *FileLoggerSuite) TestErrorShouldWriteWhenDebugIsFalse(c *check.C) {
	l := NewFileLogger("/dev/null", false)
	fl, _ := l.(*fileLogger)
	b := &bytes.Buffer{}
	fl.logger = log.New(b, "", log.LstdFlags)
	l.Error("should write this")
	c.Assert(b.String(), check.Matches, `.* ERROR: should write this\n$`)
}

func (s *FileLoggerSuite) TestGetStdLoggerShouldReturnValidLogger(c *check.C) {
	logger := s.l.GetStdLogger()
	logger.Printf(`message is "%s"`, "some debug message")
	c.Assert(s.b.String(), check.Matches, `.*message is "some debug message"\n$`)
}
<commit_msg>log: simplify test<commit_after>// Copyright 2015 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package log

import (
	"bytes"
	"log"

	"gopkg.in/check.v1"
)

type FileLoggerSuite struct {
	l  Logger
	fl *fileLogger
	b  *bytes.Buffer
}

var _ = check.Suite(&FileLoggerSuite{})

func (s *FileLoggerSuite) SetUpSuite(c *check.C) {
	s.l = NewFileLogger("/dev/null", true)
	s.fl, _ = s.l.(*fileLogger)
}

func (s *FileLoggerSuite) SetUpTest(c *check.C) {
	s.b = &bytes.Buffer{}
	s.fl.logger = log.New(s.b, "", log.LstdFlags)
}

func (s *FileLoggerSuite) TestNewFileLoggerReturnsALogger(c *check.C) {
	_, ok := s.l.(Logger)
	c.Assert(ok, check.Equals, true)
}

func (s *FileLoggerSuite) TestNewWriterLogger(c *check.C) {
	var buf bytes.Buffer
	logger := newWriterLogger(&buf, true)
	logger.Errorf("something went wrong: %s", "this")
	c.Assert(buf.String(), check.Matches, `(?m)^.*ERROR: something went wrong: this$`)
}

func (s *FileLoggerSuite) TestNewFileLoggerInitializesWriter(c *check.C) {
	c.Assert(s.fl.logger, check.FitsTypeOf, &log.Logger{})
}

func (s *FileLoggerSuite) TestErrorShouldPrefixMessage(c *check.C) {
	s.l.Error("something terrible happened")
	c.Assert(s.b.String(), check.Matches, ".* ERROR: something terrible happened\n$")
}

func (s *FileLoggerSuite) TestErrorfShouldFormatErrorAndPrefixMessage(c *check.C) {
	s.l.Errorf(`this is the error: "%s"`, "something bad happened")
	c.Assert(s.b.String(), check.Matches, `.* ERROR: this is the error: "something bad happened"\n$`)
}

func (s *FileLoggerSuite) TestDebugShouldPrefixMessage(c *check.C) {
	s.l.Debug("doing some stuff here")
	c.Assert(s.b.String(), check.Matches, ".* DEBUG: doing some stuff here\n$")
}

func (s *FileLoggerSuite) TestDebugfShouldFormatAndPrefixMessage(c *check.C) {
	s.l.Debugf(`message is "%s"`, "some debug message")
	c.Assert(s.b.String(), check.Matches, `.* DEBUG: message is "some debug message"\n$`)
}

func (s *FileLoggerSuite) TestDebugShouldNotWriteDebugIsSetToFalse(c *check.C) {
	l := NewFileLogger("/dev/null", false)
	fl, _ := l.(*fileLogger)
	b := &bytes.Buffer{}
	fl.logger = log.New(b, "", log.LstdFlags)
	l.Debug("sould not log this")
	c.Assert(b.String(), check.Equals, "")
	l.Debugf("sould not log this either %d", 1)
	c.Assert(b.String(), check.Equals, "")
}

func (s *FileLoggerSuite) TestErrorShouldWriteWhenDebugIsFalse(c *check.C) {
	l := NewFileLogger("/dev/null", false)
	fl, _ := l.(*fileLogger)
	b := &bytes.Buffer{}
	fl.logger = log.New(b, "", log.LstdFlags)
	l.Error("should write this")
	c.Assert(b.String(), check.Matches, `.* ERROR: should write this\n$`)
}

func (s *FileLoggerSuite) TestGetStdLoggerShouldReturnValidLogger(c *check.C) {
	logger := s.l.GetStdLogger()
	logger.Printf(`message is "%s"`, "some debug message")
	c.Assert(s.b.String(), check.Matches, `.*message is "some debug message"\n$`)
}
<|endoftext|>


<commit_before>package path

import (
	"os"
	"path/filepath"

	"github.com/yuin/gopher-lua"
)

// ModuleLoader defines the path module so that it can be required.
func ModuleLoader(l *lua.LState) int {
	t := l.NewTable()
	mod := l.SetFuncs(t, Exports)
	l.Push(mod)
	return 1
}

// Exports defines the exported functions in the path module.
var Exports = map[string]lua.LGFunction{
	"glob":   LuaGlob,
	"base":   LuaBase,
	"dir":    LuaDir,
	"ext":    LuaDir,
	"join":   LuaJoin,
	"exists": LuaExists,
	"is_dir": LuaIsDir,
}

// LuaGlob executes a file glob.
func LuaGlob(state *lua.LState) int {
	pattern := state.CheckString(1)

	files, err := filepath.Glob(pattern)
	if err != nil {
		state.RaiseError("%s", err.Error())
		return 0
	}
	t := state.NewTable()
	for i, file := range files {
		state.SetTable(t, lua.LNumber(i+1), lua.LString(file))
	}
	state.Push(t)
	return 1
}

// LuaBase returns the basename of the path arguent provided.
func LuaBase(state *lua.LState) int {
	path := state.CheckString(1)

	base := filepath.Base(path)
	state.Push(lua.LString(base))
	return 1
}

// LuaDir returns the parent directory of the path arguent provided.
func LuaDir(state *lua.LState) int {
	path := state.CheckString(1)

	dir := filepath.Dir(path)
	state.Push(lua.LString(dir))
	return 1
}

// LuaExt returns the file extension of the path arguent provided.
func LuaExt(state *lua.LState) int {
	path := state.CheckString(1)

	ext := filepath.Ext(path)
	state.Push(lua.LString(ext))

	return 1
}

// LuaJoin joins the provided path segments.
func LuaJoin(state *lua.LState) int {
	var segs []string

	n := state.GetTop()
	for i := 1; i <= n; i++ {
		str := state.CheckString(i)
		segs = append(segs, str)
	}
	path := filepath.Join(segs...)

	state.Push(lua.LString(path))

	return 1
}

// LuaExists returns true if the provided path segment exists.
func LuaExists(state *lua.LState) int {
	path := state.CheckString(1)
	_, err := os.Stat(path)
	if os.IsNotExist(err) {
		state.Push(lua.LBool(false))
	} else if err == nil {
		state.Push(lua.LBool(true))
	} else {
		state.RaiseError("%s", err.Error())
		return 0
	}
	return 1
}

// LuaIsDir returns true if the provided path segment exists and is a
// directory.
func LuaIsDir(state *lua.LState) int {
	path := state.CheckString(1)
	info, err := os.Stat(path)
	if os.IsNotExist(err) {
		state.Push(lua.LBool(false))
		return 1
	}
	if err != nil {
		state.RaiseError("%s", err.Error())
		return 0
	}
	state.Push(lua.LBool(info.IsDir()))
	return 1
}
<commit_msg>fix bug linking path.ext to path.dir<commit_after>package path

import (
	"os"
	"path/filepath"

	"github.com/yuin/gopher-lua"
)

// ModuleLoader defines the path module so that it can be required.
func ModuleLoader(l *lua.LState) int {
	t := l.NewTable()
	mod := l.SetFuncs(t, Exports)
	l.Push(mod)
	return 1
}

// Exports defines the exported functions in the path module.
var Exports = map[string]lua.LGFunction{
	"glob":   LuaGlob,
	"base":   LuaBase,
	"dir":    LuaDir,
	"ext":    LuaExt,
	"join":   LuaJoin,
	"exists": LuaExists,
	"is_dir": LuaIsDir,
}

// LuaGlob executes a file glob.
func LuaGlob(state *lua.LState) int {
	pattern := state.CheckString(1)

	files, err := filepath.Glob(pattern)
	if err != nil {
		state.RaiseError("%s", err.Error())
		return 0
	}
	t := state.NewTable()
	for i, file := range files {
		state.SetTable(t, lua.LNumber(i+1), lua.LString(file))
	}
	state.Push(t)
	return 1
}

// LuaBase returns the basename of the path arguent provided.
func LuaBase(state *lua.LState) int {
	path := state.CheckString(1)

	base := filepath.Base(path)
	state.Push(lua.LString(base))
	return 1
}

// LuaDir returns the parent directory of the path arguent provided.
func LuaDir(state *lua.LState) int {
	path := state.CheckString(1)

	dir := filepath.Dir(path)
	state.Push(lua.LString(dir))
	return 1
}

// LuaExt returns the file extension of the path arguent provided.
func LuaExt(state *lua.LState) int {
	path := state.CheckString(1)

	ext := filepath.Ext(path)
	state.Push(lua.LString(ext))

	return 1
}

// LuaJoin joins the provided path segments.
func LuaJoin(state *lua.LState) int {
	var segs []string

	n := state.GetTop()
	for i := 1; i <= n; i++ {
		str := state.CheckString(i)
		segs = append(segs, str)
	}
	path := filepath.Join(segs...)

	state.Push(lua.LString(path))

	return 1
}

// LuaExists returns true if the provided path segment exists.
func LuaExists(state *lua.LState) int {
	path := state.CheckString(1)
	_, err := os.Stat(path)
	if os.IsNotExist(err) {
		state.Push(lua.LBool(false))
	} else if err == nil {
		state.Push(lua.LBool(true))
	} else {
		state.RaiseError("%s", err.Error())
		return 0
	}
	return 1
}

// LuaIsDir returns true if the provided path segment exists and is a
// directory.
func LuaIsDir(state *lua.LState) int {
	path := state.CheckString(1)
	info, err := os.Stat(path)
	if os.IsNotExist(err) {
		state.Push(lua.LBool(false))
		return 1
	}
	if err != nil {
		state.RaiseError("%s", err.Error())
		return 0
	}
	state.Push(lua.LBool(info.IsDir()))
	return 1
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/mitchellh/go-homedir"
	"github.com/motemen/ghq/utils"
)

type LocalRepository struct {
	FullPath  string
	RelPath   string
	PathParts []string
}

func LocalRepositoryFromFullPath(fullPath string) (*LocalRepository, error) {
	var relPath string

	for _, root := range localRepositoryRoots() {
		if strings.HasPrefix(fullPath, root) == false {
			continue
		}

		var err error
		relPath, err = filepath.Rel(root, fullPath)
		if err == nil {
			break
		}
	}

	if relPath == "" {
		return nil, fmt.Errorf("no local repository found for: %s", fullPath)
	}

	pathParts := strings.Split(relPath, string(filepath.Separator))

	return &LocalRepository{fullPath, filepath.ToSlash(relPath), pathParts}, nil
}

func LocalRepositoryFromURL(remoteURL *url.URL) *LocalRepository {
	pathParts := append(
		[]string{remoteURL.Host}, strings.Split(remoteURL.Path, "/")...,
	)
	relPath := strings.TrimSuffix(path.Join(pathParts...), ".git")

	var localRepository *LocalRepository

	// Find existing local repository first
	walkLocalRepositories(func(repo *LocalRepository) {
		if repo.RelPath == relPath {
			localRepository = repo
		}
	})

	if localRepository != nil {
		return localRepository
	}

	// No local repository found, returning new one
	return &LocalRepository{
		path.Join(primaryLocalRepositoryRoot(), relPath),
		relPath,
		pathParts,
	}
}

// Subpaths returns lists of tail parts of relative path from the root directory (shortest first)
// for example, {"ghq", "motemen/ghq", "github.com/motemen/ghq"} for $root/github.com/motemen/ghq.
func (repo *LocalRepository) Subpaths() []string {
	tails := make([]string, len(repo.PathParts))

	for i := range repo.PathParts {
		tails[i] = strings.Join(repo.PathParts[len(repo.PathParts)-(i+1):], "/")
	}

	return tails
}

func (repo *LocalRepository) NonHostPath() string {
	return strings.Join(repo.PathParts[1:], "/")
}

func (repo *LocalRepository) IsUnderPrimaryRoot() bool {
	return strings.HasPrefix(repo.FullPath, primaryLocalRepositoryRoot())
}

// Matches checks if any subpath of the local repository equals the query.
func (repo *LocalRepository) Matches(pathQuery string) bool {
	for _, p := range repo.Subpaths() {
		if p == pathQuery {
			return true
		}
	}

	return false
}

// TODO return err
func (repo *LocalRepository) VCS() *VCSBackend {
	var (
		fi  os.FileInfo
		err error
	)

	fi, err = os.Stat(filepath.Join(repo.FullPath, ".git/svn"))
	if err == nil && fi.IsDir() {
		return GitsvnBackend
	}

	fi, err = os.Stat(filepath.Join(repo.FullPath, ".git"))
	if err == nil && fi.IsDir() {
		return GitBackend
	}

	fi, err = os.Stat(filepath.Join(repo.FullPath, ".svn"))
	if err == nil && fi.IsDir() {
		return SubversionBackend
	}

	fi, err = os.Stat(filepath.Join(repo.FullPath, ".hg"))
	if err == nil && fi.IsDir() {
		return MercurialBackend
	}

	return nil
}

var vcsDirs = []string{".git", ".svn", ".hg"}

func walkLocalRepositories(callback func(*LocalRepository)) {
	for _, root := range localRepositoryRoots() {
		filepath.Walk(root, func(path string, fileInfo os.FileInfo, err error) error {
			if err != nil || fileInfo == nil || fileInfo.IsDir() == false {
				return nil
			}

			vcsDirFound := false
			for _, d := range vcsDirs {
				_, err := os.Stat(filepath.Join(path, d))
				if err == nil {
					vcsDirFound = true
					break
				}
			}

			if !vcsDirFound {
				return nil
			}

			repo, err := LocalRepositoryFromFullPath(path)
			if err != nil {
				return nil
			}

			if repo == nil {
				return nil
			}
			callback(repo)
			return filepath.SkipDir
		})
	}
}

var _localRepositoryRoots []string

// Returns local cloned repositories' root.
// Uses the value of `git config ghq.root` or defaults to ~/.ghq.
func localRepositoryRoots() []string {
	if len(_localRepositoryRoots) != 0 {
		return _localRepositoryRoots
	}

	var err error
	_localRepositoryRoots, err = GitConfigAll("ghq.root")
	utils.PanicIf(err)

	if len(_localRepositoryRoots) == 0 {
		homeDir, err := homedir.Dir()
		utils.PanicIf(err)

		_localRepositoryRoots = []string{filepath.Join(homeDir, ".ghq")}
	}

	for i, v := range _localRepositoryRoots {
		path := filepath.Clean(v)
		if _, err := os.Stat(path); err == nil {
			_localRepositoryRoots[i], err = filepath.EvalSymlinks(path)
			utils.PanicIf(err)
		} else {
			_localRepositoryRoots[i] = path
		}
	}

	return _localRepositoryRoots
}

func primaryLocalRepositoryRoot() string {
	return localRepositoryRoots()[0]
}
<commit_msg>Skip non-VCS directories for performance<commit_after>package main

import (
	"fmt"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/mitchellh/go-homedir"
	"github.com/motemen/ghq/utils"
)

type LocalRepository struct {
	FullPath  string
	RelPath   string
	PathParts []string
}

func LocalRepositoryFromFullPath(fullPath string) (*LocalRepository, error) {
	var relPath string

	for _, root := range localRepositoryRoots() {
		if strings.HasPrefix(fullPath, root) == false {
			continue
		}

		var err error
		relPath, err = filepath.Rel(root, fullPath)
		if err == nil {
			break
		}
	}

	if relPath == "" {
		return nil, fmt.Errorf("no local repository found for: %s", fullPath)
	}

	pathParts := strings.Split(relPath, string(filepath.Separator))

	return &LocalRepository{fullPath, filepath.ToSlash(relPath), pathParts}, nil
}

func LocalRepositoryFromURL(remoteURL *url.URL) *LocalRepository {
	pathParts := append(
		[]string{remoteURL.Host}, strings.Split(remoteURL.Path, "/")...,
	)
	relPath := strings.TrimSuffix(path.Join(pathParts...), ".git")

	var localRepository *LocalRepository

	// Find existing local repository first
	walkLocalRepositories(func(repo *LocalRepository) {
		if repo.RelPath == relPath {
			localRepository = repo
		}
	})

	if localRepository != nil {
		return localRepository
	}

	// No local repository found, returning new one
	return &LocalRepository{
		path.Join(primaryLocalRepositoryRoot(), relPath),
		relPath,
		pathParts,
	}
}

// Subpaths returns lists of tail parts of relative path from the root directory (shortest first)
// for example, {"ghq", "motemen/ghq", "github.com/motemen/ghq"} for $root/github.com/motemen/ghq.
func (repo *LocalRepository) Subpaths() []string {
	tails := make([]string, len(repo.PathParts))

	for i := range repo.PathParts {
		tails[i] = strings.Join(repo.PathParts[len(repo.PathParts)-(i+1):], "/")
	}

	return tails
}

func (repo *LocalRepository) NonHostPath() string {
	return strings.Join(repo.PathParts[1:], "/")
}

func (repo *LocalRepository) IsUnderPrimaryRoot() bool {
	return strings.HasPrefix(repo.FullPath, primaryLocalRepositoryRoot())
}

// Matches checks if any subpath of the local repository equals the query.
func (repo *LocalRepository) Matches(pathQuery string) bool {
	for _, p := range repo.Subpaths() {
		if p == pathQuery {
			return true
		}
	}

	return false
}

// TODO return err
func (repo *LocalRepository) VCS() *VCSBackend {
	var (
		fi  os.FileInfo
		err error
	)

	fi, err = os.Stat(filepath.Join(repo.FullPath, ".git/svn"))
	if err == nil && fi.IsDir() {
		return GitsvnBackend
	}

	fi, err = os.Stat(filepath.Join(repo.FullPath, ".git"))
	if err == nil && fi.IsDir() {
		return GitBackend
	}

	fi, err = os.Stat(filepath.Join(repo.FullPath, ".svn"))
	if err == nil && fi.IsDir() {
		return SubversionBackend
	}

	fi, err = os.Stat(filepath.Join(repo.FullPath, ".hg"))
	if err == nil && fi.IsDir() {
		return MercurialBackend
	}

	return nil
}

var vcsDirs = []string{".git", ".svn", ".hg"}

func walkLocalRepositories(callback func(*LocalRepository)) {
	for _, root := range localRepositoryRoots() {
		filepath.Walk(root, func(path string, fileInfo os.FileInfo, err error) error {
			if err != nil || fileInfo == nil || fileInfo.IsDir() == false {
				// ghq.root can contain regular files.
				if root == filepath.Dir(path) {
					return nil
				}
				// If a regular file was found in a non-root directory, the directory
				// shouldn't be a repository.
				return filepath.SkipDir
			}
			vcsDirFound := false
			for _, d := range vcsDirs {
				_, err := os.Stat(filepath.Join(path, d))
				if err == nil {
					vcsDirFound = true
					break
				}
			}

			if !vcsDirFound {
				return nil
			}

			repo, err := LocalRepositoryFromFullPath(path)
			if err != nil {
				return nil
			}

			if repo == nil {
				return nil
			}
			callback(repo)
			return filepath.SkipDir
		})
	}
}

var _localRepositoryRoots []string

// Returns local cloned repositories' root.
// Uses the value of `git config ghq.root` or defaults to ~/.ghq.
func localRepositoryRoots() []string {
	if len(_localRepositoryRoots) != 0 {
		return _localRepositoryRoots
	}

	var err error
	_localRepositoryRoots, err = GitConfigAll("ghq.root")
	utils.PanicIf(err)

	if len(_localRepositoryRoots) == 0 {
		homeDir, err := homedir.Dir()
		utils.PanicIf(err)

		_localRepositoryRoots = []string{filepath.Join(homeDir, ".ghq")}
	}

	for i, v := range _localRepositoryRoots {
		path := filepath.Clean(v)
		if _, err := os.Stat(path); err == nil {
			_localRepositoryRoots[i], err = filepath.EvalSymlinks(path)
			utils.PanicIf(err)
		} else {
			_localRepositoryRoots[i] = path
		}
	}

	return _localRepositoryRoots
}

func primaryLocalRepositoryRoot() string {
	return localRepositoryRoots()[0]
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
)

type LocalRepository struct {
	FullPath  string
	RelPath   string
	RootPath  string
	PathParts []string

	repoPath   string
	vcsBackend *VCSBackend
}

func (repo *LocalRepository) RepoPath() string {
	if repo.repoPath != "" {
		return repo.repoPath
	}
	return repo.FullPath
}

func LocalRepositoryFromFullPath(fullPath string, backend *VCSBackend) (*LocalRepository, error) {
	var relPath string

	roots, err := localRepositoryRoots()
	if err != nil {
		return nil, err
	}
	var root string
	for _, root = range roots {
		if !strings.HasPrefix(fullPath, root) {
			continue
		}

		var err error
		relPath, err = filepath.Rel(root, fullPath)
		if err == nil {
			break
		}
	}

	if relPath == "" {
		return nil, fmt.Errorf("no local repository found for: %s", fullPath)
	}

	pathParts := strings.Split(relPath, string(filepath.Separator))

	return &LocalRepository{
		FullPath:   fullPath,
		RelPath:    filepath.ToSlash(relPath),
		RootPath:   root,
		PathParts:  pathParts,
		vcsBackend: backend,
	}, nil
}

func LocalRepositoryFromURL(remoteURL *url.URL) (*LocalRepository, error) {
	pathParts := append(
		[]string{remoteURL.Host}, strings.Split(remoteURL.Path, "/")...,
	)
	relPath := strings.TrimSuffix(path.Join(pathParts...), ".git")

	var localRepository *LocalRepository

	// Find existing local repository first
	if err := walkLocalRepositories(func(repo *LocalRepository) {
		if repo.RelPath == relPath {
			localRepository = repo
		}
	}); err != nil {
		return nil, err
	}

	if localRepository != nil {
		return localRepository, nil
	}

	prim, err := primaryLocalRepositoryRoot()
	if err != nil {
		return nil, err
	}
	// No local repository found, returning new one
	return &LocalRepository{
		FullPath:  path.Join(prim, relPath),
		RelPath:   relPath,
		RootPath:  prim,
		PathParts: pathParts,
	}, nil
}

// Subpaths returns lists of tail parts of relative path from the root directory (shortest first)
// for example, {"ghq", "motemen/ghq", "github.com/motemen/ghq"} for $root/github.com/motemen/ghq.
func (repo *LocalRepository) Subpaths() []string {
	tails := make([]string, len(repo.PathParts))

	for i := range repo.PathParts {
		tails[i] = strings.Join(repo.PathParts[len(repo.PathParts)-(i+1):], "/")
	}

	return tails
}

func (repo *LocalRepository) NonHostPath() string {
	return strings.Join(repo.PathParts[1:], "/")
}

// list as bellow
// - "$GHQ_ROOT/github.com/motemen/ghq/cmdutil" // repo.FullPath
// - "$GHQ_ROOT/github.com/motemen/ghq"
// - "$GHQ_ROOT/github.com/motemen
func (repo *LocalRepository) repoRootCandidates() []string {
	hostRoot := filepath.Join(repo.RootPath, repo.PathParts[0])
	nonHostParts := repo.PathParts[1:]
	candidates := make([]string, len(nonHostParts))
	for i := 0; i < len(nonHostParts); i++ {
		candidates[i] = filepath.Join(append(
			[]string{hostRoot}, nonHostParts[0:len(nonHostParts)-i]...)...)
	}
	return candidates
}

func (repo *LocalRepository) IsUnderPrimaryRoot() bool {
	prim, err := primaryLocalRepositoryRoot()
	if err != nil {
		return false
	}
	return strings.HasPrefix(repo.FullPath, prim)
}

// Matches checks if any subpath of the local repository equals the query.
func (repo *LocalRepository) Matches(pathQuery string) bool {
	for _, p := range repo.Subpaths() {
		if p == pathQuery {
			return true
		}
	}

	return false
}

func (repo *LocalRepository) VCS() (*VCSBackend, string) {
	if repo.vcsBackend == nil {
		for _, dir := range repo.repoRootCandidates() {
			backend := findVCSBackend(dir)
			if backend != nil {
				repo.vcsBackend = backend
				repo.repoPath = dir
				break
			}
		}
	}
	return repo.vcsBackend, repo.RepoPath()
}

var vcsContentsMap = map[string]*VCSBackend{
	".git/svn":       GitsvnBackend,
	".git":           GitBackend,
	".svn":           SubversionBackend,
	".hg":            MercurialBackend,
	"_darcs":         DarcsBackend,
	".fslckout":      FossilBackend, // file
	"_FOSSIL_":       FossilBackend, // file
	"CVS/Repository": cvsDummyBackend,
	".bzr":           BazaarBackend,
}

var vcsContents = make([]string, 0, len(vcsContentsMap))

func init() {
	for k := range vcsContentsMap {
		vcsContents = append(vcsContents, k)
	}
	// Sort in order of length.
	// This is to check git/svn before git.
	sort.Slice(vcsContents, func(i, j int) bool {
		return len(vcsContents[i]) > len(vcsContents[j])
	})
}

func findVCSBackend(fpath string) *VCSBackend {
	for _, d := range vcsContents {
		if _, err := os.Stat(filepath.Join(fpath, d)); err == nil {
			return vcsContentsMap[d]
		}
	}
	return nil
}

func walkLocalRepositories(callback func(*LocalRepository)) error {
	roots, err := localRepositoryRoots()
	if err != nil {
		return err
	}
	for _, root := range roots {
		if err := filepath.Walk(root, func(fpath string, fileInfo os.FileInfo, err error) error {
			if err != nil || fileInfo == nil {
				return nil
			}

			if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
				realpath, err := filepath.EvalSymlinks(fpath)
				if err != nil {
					return nil
				}
				fileInfo, err = os.Stat(realpath)
				if err != nil {
					return nil
				}
			}
			if !fileInfo.IsDir() {
				return nil
			}
			vcsBackend := findVCSBackend(fpath)
			if vcsBackend == nil {
				return nil
			}

			repo, err := LocalRepositoryFromFullPath(fpath, vcsBackend)
			if err != nil {
				return nil
			}

			if repo == nil {
				return nil
			}
			callback(repo)
			return filepath.SkipDir
		}); err != nil {
			return err
		}
	}
	return nil
}

var _localRepositoryRoots []string

// localRepositoryRoots returns locally cloned repositories' root directories.
// The root dirs are determined as following:
//
//   - If GHQ_ROOT environment variable is nonempty, use it as the only root dir.
//   - Otherwise, use the result of `git config --get-all ghq.root` as the dirs.
//   - Otherwise, fallback to the default root, `~/.ghq`.
func localRepositoryRoots() ([]string, error) {
	if len(_localRepositoryRoots) != 0 {
		return _localRepositoryRoots, nil
	}

	envRoot := os.Getenv("GHQ_ROOT")
	if envRoot != "" {
		_localRepositoryRoots = filepath.SplitList(envRoot)
	} else {
		var err error
		if _localRepositoryRoots, err = GitConfigAll("ghq.root"); err != nil {
			return nil, err
		}
	}

	if len(_localRepositoryRoots) == 0 {
		homeDir, err := os.UserHomeDir()
		if err != nil {
			return nil, err
		}
		_localRepositoryRoots = []string{filepath.Join(homeDir, ".ghq")}
	}

	for i, v := range _localRepositoryRoots {
		path := filepath.Clean(v)
		if _, err := os.Stat(path); err == nil {
			if path, err = filepath.EvalSymlinks(path); err != nil {
				return nil, err
			}
		}
		if !filepath.IsAbs(path) {
			var err error
			if path, err = filepath.Abs(path); err != nil {
				return nil, err
			}
		}
		_localRepositoryRoots[i] = path
	}

	return _localRepositoryRoots, nil
}

func primaryLocalRepositoryRoot() (string, error) {
	roots, err := localRepositoryRoots()
	if err != nil {
		return "", err
	}
	return roots[0], nil
}
<commit_msg>adjustment<commit_after>package main

import (
	"fmt"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
)

type LocalRepository struct {
	FullPath  string
	RelPath   string
	RootPath  string
	PathParts []string

	repoPath   string
	vcsBackend *VCSBackend
}

func (repo *LocalRepository) RepoPath() string {
	if repo.repoPath != "" {
		return repo.repoPath
	}
	return repo.FullPath
}

func LocalRepositoryFromFullPath(fullPath string, backend *VCSBackend) (*LocalRepository, error) {
	var relPath string

	roots, err := localRepositoryRoots()
	if err != nil {
		return nil, err
	}
	var root string
	for _, root = range roots {
		if !strings.HasPrefix(fullPath, root) {
			continue
		}

		var err error
		relPath, err = filepath.Rel(root, fullPath)
		if err == nil {
			break
		}
	}

	if relPath == "" {
		return nil, fmt.Errorf("no local repository found for: %s", fullPath)
	}

	pathParts := strings.Split(relPath, string(filepath.Separator))

	return &LocalRepository{
		FullPath:   fullPath,
		RelPath:    filepath.ToSlash(relPath),
		RootPath:   root,
		PathParts:  pathParts,
		vcsBackend: backend,
	}, nil
}

func LocalRepositoryFromURL(remoteURL *url.URL) (*LocalRepository, error) {
	pathParts := append(
		[]string{remoteURL.Host}, strings.Split(remoteURL.Path, "/")...,
	)
	relPath := strings.TrimSuffix(path.Join(pathParts...), ".git")

	var localRepository *LocalRepository

	// Find existing local repository first
	if err := walkLocalRepositories(func(repo *LocalRepository) {
		if repo.RelPath == relPath {
			localRepository = repo
		}
	}); err != nil {
		return nil, err
	}

	if localRepository != nil {
		return localRepository, nil
	}

	prim, err := primaryLocalRepositoryRoot()
	if err != nil {
		return nil, err
	}
	// No local repository found, returning new one
	return &LocalRepository{
		FullPath:  path.Join(prim, relPath),
		RelPath:   relPath,
		RootPath:  prim,
		PathParts: pathParts,
	}, nil
}

// Subpaths returns lists of tail parts of relative path from the root directory (shortest first)
// for example, {"ghq", "motemen/ghq", "github.com/motemen/ghq"} for $root/github.com/motemen/ghq.
func (repo *LocalRepository) Subpaths() []string {
	tails := make([]string, len(repo.PathParts))

	for i := range repo.PathParts {
		tails[i] = strings.Join(repo.PathParts[len(repo.PathParts)-(i+1):], "/")
	}

	return tails
}

func (repo *LocalRepository) NonHostPath() string {
	return strings.Join(repo.PathParts[1:], "/")
}

// list as bellow
// - "$GHQ_ROOT/github.com/motemen/ghq/cmdutil" // repo.FullPath
// - "$GHQ_ROOT/github.com/motemen/ghq"
// - "$GHQ_ROOT/github.com/motemen
func (repo *LocalRepository) repoRootCandidates() []string {
	hostRoot := filepath.Join(repo.RootPath, repo.PathParts[0])
	nonHostParts := repo.PathParts[1:]
	candidates := make([]string, len(nonHostParts))
	for i := 0; i < len(nonHostParts); i++ {
		candidates[i] = filepath.Join(append(
			[]string{hostRoot}, nonHostParts[0:len(nonHostParts)-i]...)...)
	}
	return candidates
}

func (repo *LocalRepository) IsUnderPrimaryRoot() bool {
	prim, err := primaryLocalRepositoryRoot()
	if err != nil {
		return false
	}
	return strings.HasPrefix(repo.FullPath, prim)
}

// Matches checks if any subpath of the local repository equals the query.
func (repo *LocalRepository) Matches(pathQuery string) bool {
	for _, p := range repo.Subpaths() {
		if p == pathQuery {
			return true
		}
	}

	return false
}

func (repo *LocalRepository) VCS() (*VCSBackend, string) {
	if repo.vcsBackend == nil {
		for _, dir := range repo.repoRootCandidates() {
			backend := findVCSBackend(dir)
			if backend != nil {
				repo.vcsBackend = backend
				repo.repoPath = dir
				break
			}
		}
	}
	return repo.vcsBackend, repo.RepoPath()
}

var vcsContentsMap = map[string]*VCSBackend{
	".git/svn":       GitsvnBackend,
	".git":           GitBackend,
	".svn":           SubversionBackend,
	".hg":            MercurialBackend,
	"_darcs":         DarcsBackend,
	".fslckout":      FossilBackend, // file
	"_FOSSIL_":       FossilBackend, // file
	"CVS/Repository": cvsDummyBackend,
	".bzr":           BazaarBackend,
}

var vcsContents = make([]string, 0, len(vcsContentsMap))

func init() {
	for k := range vcsContentsMap {
		vcsContents = append(vcsContents, k)
	}
	// Sort in order of length.
	// This is to check git/svn before git.
	sort.Slice(vcsContents, func(i, j int) bool {
		return len(vcsContents[i]) > len(vcsContents[j])
	})
}

func findVCSBackend(fpath string) *VCSBackend {
	for _, d := range vcsContents {
		if _, err := os.Stat(filepath.Join(fpath, d)); err == nil {
			return vcsContentsMap[d]
		}
	}
	return nil
}

func walkLocalRepositories(callback func(*LocalRepository)) error {
	roots, err := localRepositoryRoots()
	if err != nil {
		return err
	}
	for _, root := range roots {
		if err := filepath.Walk(root, func(fpath string, fileInfo os.FileInfo, err error) error {
			if err != nil || fileInfo == nil {
				return nil
			}

			if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
				realpath, err := filepath.EvalSymlinks(fpath)
				if err != nil {
					return nil
				}
				fileInfo, err = os.Stat(realpath)
				if err != nil {
					return nil
				}
			}
			if !fileInfo.IsDir() {
				return nil
			}
			vcsBackend := findVCSBackend(fpath)
			if vcsBackend == nil {
				return nil
			}

			repo, err := LocalRepositoryFromFullPath(fpath, vcsBackend)
			if err != nil || repo == nil {
				return nil
			}
			callback(repo)
			return filepath.SkipDir
		}); err != nil {
			return err
		}
	}
	return nil
}

var _localRepositoryRoots []string

// localRepositoryRoots returns locally cloned repositories' root directories.
// The root dirs are determined as following:
//
//   - If GHQ_ROOT environment variable is nonempty, use it as the only root dir.
//   - Otherwise, use the result of `git config --get-all ghq.root` as the dirs.
//   - Otherwise, fallback to the default root, `~/.ghq`.
func localRepositoryRoots() ([]string, error) {
	if len(_localRepositoryRoots) != 0 {
		return _localRepositoryRoots, nil
	}

	envRoot := os.Getenv("GHQ_ROOT")
	if envRoot != "" {
		_localRepositoryRoots = filepath.SplitList(envRoot)
	} else {
		var err error
		if _localRepositoryRoots, err = GitConfigAll("ghq.root"); err != nil {
			return nil, err
		}
	}

	if len(_localRepositoryRoots) == 0 {
		homeDir, err := os.UserHomeDir()
		if err != nil {
			return nil, err
		}
		_localRepositoryRoots = []string{filepath.Join(homeDir, ".ghq")}
	}

	for i, v := range _localRepositoryRoots {
		path := filepath.Clean(v)
		if _, err := os.Stat(path); err == nil {
			if path, err = filepath.EvalSymlinks(path); err != nil {
				return nil, err
			}
		}
		if !filepath.IsAbs(path) {
			var err error
			if path, err = filepath.Abs(path); err != nil {
				return nil, err
			}
		}
		_localRepositoryRoots[i] = path
	}

	return _localRepositoryRoots, nil
}

func primaryLocalRepositoryRoot() (string, error) {
	roots, err := localRepositoryRoots()
	if err != nil {
		return "", err
	}
	return roots[0], nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
)

type LocalRepository struct {
	FullPath  string
	RelPath   string
	RootPath  string
	PathParts []string

	repoPath   string
	vcsBackend *VCSBackend
}

func (repo *LocalRepository) RepoPath() string {
	if repo.repoPath != "" {
		return repo.repoPath
	}
	return repo.FullPath
}

func LocalRepositoryFromFullPath(fullPath string, backend *VCSBackend) (*LocalRepository, error) {
	var relPath string

	roots, err := localRepositoryRoots()
	if err != nil {
		return nil, err
	}
	var root string
	for _, root = range roots {
		if !strings.HasPrefix(fullPath, root) {
			continue
		}

		var err error
		relPath, err = filepath.Rel(root, fullPath)
		if err == nil {
			break
		}
	}

	if relPath == "" {
		return nil, fmt.Errorf("no local repository found for: %s", fullPath)
	}

	pathParts := strings.Split(relPath, string(filepath.Separator))

	return &LocalRepository{
		FullPath:   fullPath,
		RelPath:    filepath.ToSlash(relPath),
		RootPath:   root,
		PathParts:  pathParts,
		vcsBackend: backend,
	}, nil
}

func LocalRepositoryFromURL(remoteURL *url.URL) (*LocalRepository, error) {
	pathParts := append(
		[]string{remoteURL.Host}, strings.Split(remoteURL.Path, "/")...,
	)
	relPath := strings.TrimSuffix(path.Join(pathParts...), ".git")

	var localRepository *LocalRepository

	// Find existing local repository first
	if err := walkLocalRepositories(func(repo *LocalRepository) {
		if repo.RelPath == relPath {
			localRepository = repo
		}
	}); err != nil {
		return nil, err
	}

	if localRepository != nil {
		return localRepository, nil
	}

	prim, err := primaryLocalRepositoryRoot()
	if err != nil {
		return nil, err
	}
	// No local repository found, returning new one
	return &LocalRepository{
		FullPath:  path.Join(prim, relPath),
		RelPath:   relPath,
		RootPath:  prim,
		PathParts: pathParts,
	}, nil
}

// Subpaths returns lists of tail parts of relative path from the root directory (shortest first)
// for example, {"ghq", "motemen/ghq", "github.com/motemen/ghq"} for $root/github.com/motemen/ghq.
func (repo *LocalRepository) Subpaths() []string {
	tails := make([]string, len(repo.PathParts))

	for i := range repo.PathParts {
		tails[i] = strings.Join(repo.PathParts[len(repo.PathParts)-(i+1):], "/")
	}

	return tails
}

func (repo *LocalRepository) NonHostPath() string {
	return strings.Join(repo.PathParts[1:], "/")
}

func (repo *LocalRepository) repoRootCandidates() []string {
	hostRoot := filepath.Join(repo.RootPath, repo.PathParts[0])
	nonHostParts := repo.PathParts[1:]
	candidates := make([]string, len(nonHostParts))
	for i := 0; i < len(nonHostParts); i++ {
		candidates[i] = filepath.Join(append(
			[]string{hostRoot}, nonHostParts[0:len(nonHostParts)-i]...)...)
	}
	return candidates
}

func (repo *LocalRepository) IsUnderPrimaryRoot() bool {
	prim, err := primaryLocalRepositoryRoot()
	if err != nil {
		return false
	}
	return strings.HasPrefix(repo.FullPath, prim)
}

// Matches checks if any subpath of the local repository equals the query.
func (repo *LocalRepository) Matches(pathQuery string) bool {
	for _, p := range repo.Subpaths() {
		if p == pathQuery {
			return true
		}
	}

	return false
}

func (repo *LocalRepository) VCS() (*VCSBackend, string) {
	if repo.vcsBackend == nil {
		for _, dir := range repo.repoRootCandidates() {
			backend := findVCSBackend(dir)
			if backend != nil {
				repo.vcsBackend = backend
				repo.repoPath = dir
				break
			}
		}
	}
	return repo.vcsBackend, repo.RepoPath()
}

var vcsContentsMap = map[string]*VCSBackend{
	".git/svn":       GitsvnBackend,
	".git":           GitBackend,
	".svn":           SubversionBackend,
	".hg":            MercurialBackend,
	"_darcs":         DarcsBackend,
	".fslckout":      FossilBackend, // file
	"_FOSSIL_":       FossilBackend, // file
	"CVS/Repository": cvsDummyBackend,
	".bzr":           BazaarBackend,
}

var vcsContents = make([]string, 0, len(vcsContentsMap))

func init() {
	for k := range vcsContentsMap {
		vcsContents = append(vcsContents, k)
	}
	// Sort in order of length.
	// This is to check git/svn before git.
	sort.Slice(vcsContents, func(i, j int) bool {
		return len(vcsContents[i]) > len(vcsContents[j])
	})
}

func findVCSBackend(fpath string) *VCSBackend {
	for _, d := range vcsContents {
		if _, err := os.Stat(filepath.Join(fpath, d)); err == nil {
			return vcsContentsMap[d]
		}
	}
	return nil
}

func walkLocalRepositories(callback func(*LocalRepository)) error {
	roots, err := localRepositoryRoots()
	if err != nil {
		return err
	}
	for _, root := range roots {
		if err := filepath.Walk(root, func(fpath string, fileInfo os.FileInfo, err error) error {
			if err != nil || fileInfo == nil {
				return nil
			}

			if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
				realpath, err := filepath.EvalSymlinks(fpath)
				if err != nil {
					return nil
				}
				fileInfo, err = os.Stat(realpath)
				if err != nil {
					return nil
				}
			}
			if !fileInfo.IsDir() {
				return nil
			}
			vcsBackend := findVCSBackend(fpath)
			if vcsBackend == nil {
				return nil
			}

			repo, err := LocalRepositoryFromFullPath(fpath, vcsBackend)
			if err != nil {
				return nil
			}

			if repo == nil {
				return nil
			}
			callback(repo)
			return filepath.SkipDir
		}); err != nil {
			return err
		}
	}
	return nil
}

var _localRepositoryRoots []string

// localRepositoryRoots returns locally cloned repositories' root directories.
// The root dirs are determined as following:
//
//   - If GHQ_ROOT environment variable is nonempty, use it as the only root dir.
//   - Otherwise, use the result of `git config --get-all ghq.root` as the dirs.
//   - Otherwise, fallback to the default root, `~/.ghq`.
func localRepositoryRoots() ([]string, error) {
	if len(_localRepositoryRoots) != 0 {
		return _localRepositoryRoots, nil
	}

	envRoot := os.Getenv("GHQ_ROOT")
	if envRoot != "" {
		_localRepositoryRoots = filepath.SplitList(envRoot)
	} else {
		var err error
		if _localRepositoryRoots, err = GitConfigAll("ghq.root"); err != nil {
			return nil, err
		}
	}

	if len(_localRepositoryRoots) == 0 {
		homeDir, err := os.UserHomeDir()
		if err != nil {
			return nil, err
		}
		_localRepositoryRoots = []string{filepath.Join(homeDir, ".ghq")}
	}

	for i, v := range _localRepositoryRoots {
		path := filepath.Clean(v)
		if _, err := os.Stat(path); err == nil {
			if path, err = filepath.EvalSymlinks(path); err != nil {
				return nil, err
			}
		}
		if !filepath.IsAbs(path) {
			var err error
			if path, err = filepath.Abs(path); err != nil {
				return nil, err
			}
		}
		_localRepositoryRoots[i] = path
	}

	return _localRepositoryRoots, nil
}

func primaryLocalRepositoryRoot() (string, error) {
	roots, err := localRepositoryRoots()
	if err != nil {
		return "", err
	}
	return roots[0], nil
}
<commit_msg>add comment<commit_after>package main

import (
	"fmt"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
)

type LocalRepository struct {
	FullPath  string
	RelPath   string
	RootPath  string
	PathParts []string

	repoPath   string
	vcsBackend *VCSBackend
}

func (repo *LocalRepository) RepoPath() string {
	if repo.repoPath != "" {
		return repo.repoPath
	}
	return repo.FullPath
}

func LocalRepositoryFromFullPath(fullPath string, backend *VCSBackend) (*LocalRepository, error) {
	var relPath string

	roots, err := localRepositoryRoots()
	if err != nil {
		return nil, err
	}
	var root string
	for _, root = range roots {
		if !strings.HasPrefix(fullPath, root) {
			continue
		}

		var err error
		relPath, err = filepath.Rel(root, fullPath)
		if err == nil {
			break
		}
	}

	if relPath == "" {
		return nil, fmt.Errorf("no local repository found for: %s", fullPath)
	}

	pathParts := strings.Split(relPath, string(filepath.Separator))

	return &LocalRepository{
		FullPath:   fullPath,
		RelPath:    filepath.ToSlash(relPath),
		RootPath:   root,
		PathParts:  pathParts,
		vcsBackend: backend,
	}, nil
}

func LocalRepositoryFromURL(remoteURL *url.URL) (*LocalRepository, error) {
	pathParts := append(
		[]string{remoteURL.Host}, strings.Split(remoteURL.Path, "/")...,
	)
	relPath := strings.TrimSuffix(path.Join(pathParts...), ".git")

	var localRepository *LocalRepository

	// Find existing local repository first
	if err := walkLocalRepositories(func(repo *LocalRepository) {
		if repo.RelPath == relPath {
			localRepository = repo
		}
	}); err != nil {
		return nil, err
	}

	if localRepository != nil {
		return localRepository, nil
	}

	prim, err := primaryLocalRepositoryRoot()
	if err != nil {
		return nil, err
	}
	// No local repository found, returning new one
	return &LocalRepository{
		FullPath:  path.Join(prim, relPath),
		RelPath:   relPath,
		RootPath:  prim,
		PathParts: pathParts,
	}, nil
}

// Subpaths returns lists of tail parts of relative path from the root directory (shortest first)
// for example, {"ghq", "motemen/ghq", "github.com/motemen/ghq"} for $root/github.com/motemen/ghq.
func (repo *LocalRepository) Subpaths() []string {
	tails := make([]string, len(repo.PathParts))

	for i := range repo.PathParts {
		tails[i] = strings.Join(repo.PathParts[len(repo.PathParts)-(i+1):], "/")
	}

	return tails
}

func (repo *LocalRepository) NonHostPath() string {
	return strings.Join(repo.PathParts[1:], "/")
}

// list as bellow
// - "$GHQ_ROOT/github.com/motemen/ghq/cmdutil" // repo.FullPath
// - "$GHQ_ROOT/github.com/motemen/ghq"
// - "$GHQ_ROOT/github.com/motemen
func (repo *LocalRepository) repoRootCandidates() []string {
	hostRoot := filepath.Join(repo.RootPath, repo.PathParts[0])
	nonHostParts := repo.PathParts[1:]
	candidates := make([]string, len(nonHostParts))
	for i := 0; i < len(nonHostParts); i++ {
		candidates[i] = filepath.Join(append(
			[]string{hostRoot}, nonHostParts[0:len(nonHostParts)-i]...)...)
	}
	return candidates
}

func (repo *LocalRepository) IsUnderPrimaryRoot() bool {
	prim, err := primaryLocalRepositoryRoot()
	if err != nil {
		return false
	}
	return strings.HasPrefix(repo.FullPath, prim)
}

// Matches checks if any subpath of the local repository equals the query.
func (repo *LocalRepository) Matches(pathQuery string) bool {
	for _, p := range repo.Subpaths() {
		if p == pathQuery {
			return true
		}
	}

	return false
}

func (repo *LocalRepository) VCS() (*VCSBackend, string) {
	if repo.vcsBackend == nil {
		for _, dir := range repo.repoRootCandidates() {
			backend := findVCSBackend(dir)
			if backend != nil {
				repo.vcsBackend = backend
				repo.repoPath = dir
				break
			}
		}
	}
	return repo.vcsBackend, repo.RepoPath()
}

var vcsContentsMap = map[string]*VCSBackend{
	".git/svn":       GitsvnBackend,
	".git":           GitBackend,
	".svn":           SubversionBackend,
	".hg":            MercurialBackend,
	"_darcs":         DarcsBackend,
	".fslckout":      FossilBackend, // file
	"_FOSSIL_":       FossilBackend, // file
	"CVS/Repository": cvsDummyBackend,
	".bzr":           BazaarBackend,
}

var vcsContents = make([]string, 0, len(vcsContentsMap))

func init() {
	for k := range vcsContentsMap {
		vcsContents = append(vcsContents, k)
	}
	// Sort in order of length.
	// This is to check git/svn before git.
	sort.Slice(vcsContents, func(i, j int) bool {
		return len(vcsContents[i]) > len(vcsContents[j])
	})
}

func findVCSBackend(fpath string) *VCSBackend {
	for _, d := range vcsContents {
		if _, err := os.Stat(filepath.Join(fpath, d)); err == nil {
			return vcsContentsMap[d]
		}
	}
	return nil
}

func walkLocalRepositories(callback func(*LocalRepository)) error {
	roots, err := localRepositoryRoots()
	if err != nil {
		return err
	}
	for _, root := range roots {
		if err := filepath.Walk(root, func(fpath string, fileInfo os.FileInfo, err error) error {
			if err != nil || fileInfo == nil {
				return nil
			}

			if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
				realpath, err := filepath.EvalSymlinks(fpath)
				if err != nil {
					return nil
				}
				fileInfo, err = os.Stat(realpath)
				if err != nil {
					return nil
				}
			}
			if !fileInfo.IsDir() {
				return nil
			}
			vcsBackend := findVCSBackend(fpath)
			if vcsBackend == nil {
				return nil
			}

			repo, err := LocalRepositoryFromFullPath(fpath, vcsBackend)
			if err != nil {
				return nil
			}

			if repo == nil {
				return nil
			}
			callback(repo)
			return filepath.SkipDir
		}); err != nil {
			return err
		}
	}
	return nil
}

var _localRepositoryRoots []string

// localRepositoryRoots returns locally cloned repositories' root directories.
// The root dirs are determined as following:
//
//   - If GHQ_ROOT environment variable is nonempty, use it as the only root dir.
//   - Otherwise, use the result of `git config --get-all ghq.root` as the dirs.
//   - Otherwise, fallback to the default root, `~/.ghq`.
func localRepositoryRoots() ([]string, error) {
	if len(_localRepositoryRoots) != 0 {
		return _localRepositoryRoots, nil
	}

	envRoot := os.Getenv("GHQ_ROOT")
	if envRoot != "" {
		_localRepositoryRoots = filepath.SplitList(envRoot)
	} else {
		var err error
		if _localRepositoryRoots, err = GitConfigAll("ghq.root"); err != nil {
			return nil, err
		}
	}

	if len(_localRepositoryRoots) == 0 {
		homeDir, err := os.UserHomeDir()
		if err != nil {
			return nil, err
		}
		_localRepositoryRoots = []string{filepath.Join(homeDir, ".ghq")}
	}

	for i, v := range _localRepositoryRoots {
		path := filepath.Clean(v)
		if _, err := os.Stat(path); err == nil {
			if path, err = filepath.EvalSymlinks(path); err != nil {
				return nil, err
			}
		}
		if !filepath.IsAbs(path) {
			var err error
			if path, err = filepath.Abs(path); err != nil {
				return nil, err
			}
		}
		_localRepositoryRoots[i] = path
	}

	return _localRepositoryRoots, nil
}

func primaryLocalRepositoryRoot() (string, error) {
	roots, err := localRepositoryRoots()
	if err != nil {
		return "", err
	}
	return roots[0], nil
}
<|endoftext|>


<commit_before>package mediasort

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/jpillora/media-sort/search"
)

type fileSorter struct {
	name, path string
	info       os.FileInfo
	err        error

	s               *Sorter
	query           string
	ext             string
	mtype           string
	season, episode string
	year            string
}

func newFileSorter(s *Sorter, path string, info os.FileInfo) (*fileSorter, error) {
	//attempt to rule out file
	if !info.Mode().IsRegular() {
		return nil, nil
	}
	name := info.Name()
	ext := filepath.Ext(name)
	if _, exists := s.exts[ext]; !exists {
		return nil, nil
	}
	name = strings.TrimSuffix(name, ext)

	//setup
	return &fileSorter{
		s:       s,
		name:    name,
		path:    path,
		info:    info,
		query:   name,
		ext:     ext,
		season:  "1",
		episode: "1",
	}, nil
}

var junk = regexp.MustCompile(`\b(720p|1080p|hdtv|x264|dts|bluray)\b`)
var nonalpha = regexp.MustCompile(`[^A-Za-z0-9]`)
var spaces = regexp.MustCompile(`\s+`)
var episeason = regexp.MustCompile(`^(.+?)\bs?(eason)?(\d{1,2})(e|\ |\ e|x|xe)(pisode)?(\d{1,2})\b`)
var year = regexp.MustCompile(`^(.+?[^\d])(\d{4})[^\d]`)
var partnum = regexp.MustCompile(`^(.+?\b)(\d{1,2})\b`)

//TODO var romannumerals...

//always in a goroutine
func (f *fileSorter) goRun(wg *sync.WaitGroup) {
	f.err = f.run()
	wg.Done()
}

func (f *fileSorter) run() error {

	//normalize name
	f.query = strings.ToLower(f.query)
	f.query = strings.Replace(f.query, ".", " ", -1)
	f.query = nonalpha.ReplaceAllString(f.query, " ")
	f.query = junk.ReplaceAllString(f.query, "")
	f.query = spaces.ReplaceAllString(f.query, " ")

	//extract episde season numbers if they exist
	m := episeason.FindStringSubmatch(f.query)
	if len(m) > 0 {
		f.query = m[1] //trim name
		f.mtype = "series"
		f.season = m[3]
		f.episode = m[6]
	}

	//extract movie year
	if f.mtype == "" {
		m = year.FindStringSubmatch(f.query)
		if len(m) > 0 {
			f.query = m[1] //trim name
			f.mtype = "movie"
			f.year = m[2]
		}
	}

	//if the above fails, extract "Part 1/2/3..."
	if f.mtype == "" {
		m = partnum.FindStringSubmatch(f.query)
		if len(m) > 0 {
			f.query = m[1] //trim name
			f.mtype = "series"
			f.episode = m[2]
		}
	}

	// if f.mtype == "" {
	// 	return fmt.Errorf("No season/episode or year found")
	// }

	//trim spaces
	f.query = strings.TrimSpace(f.query)

	//search for normalized name
	r, err := search.Do(f.query, f.mtype)
	if err != nil {
		//not found
		return err
	}

	//calculate destination path
	dest := ""
	if r.Type == "series" {
		s, _ := strconv.Atoi(f.season)
		e, _ := strconv.Atoi(f.episode)
		filename := fmt.Sprintf("%s S%02dE%02d%s", r.Title, s, e, f.ext)
		dest = filepath.Join(f.s.c.TVDir, r.Title, filename)
	} else {
		filename := fmt.Sprintf("%s (%s)%s", r.Title, f.year, f.ext)
		dest = filepath.Join(f.s.c.MovieDir, filename)
	}

	//DEBUG
	// log.Printf("SUCCESS = D%d #%d\n  %s\n  %s", r.Distance, len(f.query), f.query, r.Title)
	log.Printf("Moving\n  '%s'\n  └─> '%s'", f.path, dest)

	if f.s.c.DryRun {
		return nil
	}

	//check already exists
	if _, err := os.Stat(dest); os.IsExist(err) {
		return fmt.Errorf("File already exists '%s'", dest)
	}

	//mkdir -p
	err = os.MkdirAll(filepath.Dir(dest), 0755)
	if err != nil {
		return err //failed to mkdir
	}

	//mv
	err = os.Rename(f.path, dest)
	if err != nil {
		return err //failed to move
	}
	return nil
}
<commit_msg>attempt #2 fix silly bug<commit_after>package mediasort

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/jpillora/media-sort/search"
)

type fileSorter struct {
	name, path string
	info       os.FileInfo
	err        error

	s               *Sorter
	query           string
	ext             string
	mtype           string
	season, episode string
	year            string
}

func newFileSorter(s *Sorter, path string, info os.FileInfo) (*fileSorter, error) {
	//attempt to rule out file
	if !info.Mode().IsRegular() {
		return nil, nil
	}
	name := info.Name()
	ext := filepath.Ext(name)
	if _, exists := s.exts[ext]; !exists {
		return nil, nil
	}
	name = strings.TrimSuffix(name, ext)

	//setup
	return &fileSorter{
		s:       s,
		name:    name,
		path:    path,
		info:    info,
		query:   name,
		ext:     ext,
		season:  "1",
		episode: "1",
	}, nil
}

var junk = regexp.MustCompile(`\b(720p|1080p|hdtv|x264|dts|bluray)\b`)
var nonalpha = regexp.MustCompile(`[^A-Za-z0-9]`)
var spaces = regexp.MustCompile(`\s+`)
var episeason = regexp.MustCompile(`^(.+?)\bs?(eason)?(\d{1,2})(e|\ |\ e|x|xe)(pisode)?(\d{1,2})\b`)
var year = regexp.MustCompile(`^(.+?[^\d])(\d{4})[^\d]`)
var partnum = regexp.MustCompile(`^(.+?\b)(\d{1,2})\b`)

//TODO var romannumerals...

//always in a goroutine
func (f *fileSorter) goRun(wg *sync.WaitGroup) {
	f.err = f.run()
	wg.Done()
}

func (f *fileSorter) run() error {

	//normalize name
	f.query = strings.ToLower(f.query)
	f.query = strings.Replace(f.query, ".", " ", -1)
	f.query = nonalpha.ReplaceAllString(f.query, " ")
	f.query = junk.ReplaceAllString(f.query, "")
	f.query = spaces.ReplaceAllString(f.query, " ")

	//extract episde season numbers if they exist
	m := episeason.FindStringSubmatch(f.query)
	if len(m) > 0 {
		f.query = m[1] //trim name
		f.mtype = "series"
		f.season = m[3]
		f.episode = m[6]
	}

	//extract movie year
	if f.mtype == "" {
		m = year.FindStringSubmatch(f.query)
		if len(m) > 0 {
			f.query = m[1] //trim name
			f.mtype = "movie"
			f.year = m[2]
		}
	}

	//if the above fails, extract "Part 1/2/3..."
	if f.mtype == "" {
		m = partnum.FindStringSubmatch(f.query)
		if len(m) > 0 {
			f.query = m[1] //trim name
			f.mtype = "series"
			f.episode = m[2]
		}
	}

	// if f.mtype == "" {
	// 	return fmt.Errorf("No season/episode or year found")
	// }

	//trim spaces
	f.query = strings.TrimSpace(f.query)

	//search for normalized name
	r, err := search.Do(f.query, f.mtype)
	if err != nil {
		//not found
		return err
	}

	//calculate destination path
	dest := ""
	if r.Type == "series" {
		s, _ := strconv.Atoi(f.season)
		e, _ := strconv.Atoi(f.episode)
		filename := fmt.Sprintf("%s S%02dE%02d%s", r.Title, s, e, f.ext)
		dest = filepath.Join(f.s.c.TVDir, r.Title, filename)
	} else {
		filename := fmt.Sprintf("%s (%s)%s", r.Title, f.year, f.ext)
		dest = filepath.Join(f.s.c.MovieDir, filename)
	}

	//DEBUG
	// log.Printf("SUCCESS = D%d #%d\n  %s\n  %s", r.Distance, len(f.query), f.query, r.Title)
	log.Printf("Moving\n  '%s'\n  └─> '%s'", f.path, dest)

	if f.s.c.DryRun {
		return nil
	}

	//check already exists
	if _, err := os.Stat(dest); err == nil {
		return fmt.Errorf("File already exists '%s'", dest)
	}

	//mkdir -p
	err = os.MkdirAll(filepath.Dir(dest), 0755)
	if err != nil {
		return err //failed to mkdir
	}

	//mv
	err = os.Rename(f.path, dest)
	if err != nil {
		return err //failed to move
	}
	return nil
}
<|endoftext|>


<commit_before>package message

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/shirou/gopsutil/cpu"
	"github.com/shirou/gopsutil/net"
	"github.com/shirou/gopsutil/process"
	"github.com/tychoish/grip/level"
)

// ProcessInfo holds the data for per-process statistics (e.g. cpu,
// memory, io). The Process info composers produce messages in this
// form.
type ProcessInfo struct {
	Message        string                   `json:"message,omitempty" bson:"message,omitempty"`
	Pid            int32                    `json:"pid" bson:"pid"`
	Parent         int32                    `json:"parentPid,omitempty" bson:"parentPid,omitempty"`
	Threads        int                      `json:"numThreads,omitempty" bson:"numThreads,omitempty"`
	Command        string                   `json:"command,omitempty" bson:"command,omitempty"`
	CPU            cpu.TimesStat            `json:"cpu,omitempty" bson:"cpu,omitempty"`
	IoStat         process.IOCountersStat   `json:"io,omitempty" bson:"io,omitempty"`
	NetStat        []net.IOCountersStat     `json:"net,omitempty" bson:"net,omitempty"`
	Memory         process.MemoryInfoStat   `json:"mem,omitempty" bson:"mem,omitempty"`
	MemoryPlatform process.MemoryInfoExStat `json:"memExtra,omitempty" bson:"memExtra,omitempty"`
	Errors         []string                 `json:"errors,omitempty" bson:"errors,omitempty"`
	Base           `json:"metadata,omitempty" bson:"metadata,omitempty"`
	loggable       bool
	rendered       string
}

///////////////////////////////////////////////////////////////////////////
//
// Constructors
//
///////////////////////////////////////////////////////////////////////////

// CollectProcessInfo returns a populated ProcessInfo message.Composer
// instance for the specified pid.
func CollectProcessInfo(pid int32) Composer {
	return NewProcessInfo(level.Trace, pid, "")
}

// CollectProcessInfoSelf returns a populated ProcessInfo message.Composer
// for the pid of the current process.
func CollectProcessInfoSelf() Composer {
	return NewProcessInfo(level.Trace, int32(os.Getpid()), "")
}

// CollectProcessInfoSelfWithChildren returns a slice of populated
// ProcessInfo message.Composer instances for the current process and
// all children processes.
func CollectProcessInfoSelfWithChildren() []Composer {
	return CollectProcessInfoWithChildren(int32(os.Getpid()))
}

// CollectProcessInfoWithChildren returns a slice of populated
// ProcessInfo message.Composer instances for the process with the
// specified pid and all children processes for that process.
func CollectProcessInfoWithChildren(pid int32) []Composer {
	var results []Composer
	parent, err := process.NewProcess(pid)
	if err != nil {
		return results
	}

	parentMsg := &ProcessInfo{}
	parentMsg.loggable = true
	parentMsg.populate(parent)
	results = append(results, parentMsg)

	children, err := parent.Children()
	parentMsg.saveError(err)
	if err != nil {
		return results
	}

	for _, child := range children {
		cm := &ProcessInfo{}
		cm.loggable = true
		cm.populate(child)
		results = append(results, cm)
	}

	return results
}

// NewProcessInfo constructs a fully configured and populated
// Processinfo message.Composer instance for the specified process.
func NewProcessInfo(priority level.Priority, pid int32, message string) Composer {
	p := &ProcessInfo{
		Message: message,
		Pid:     pid,
	}

	if err := p.SetPriority(priority); err != nil {
		p.saveError(err)
		return p
	}

	proc, err := process.NewProcess(pid)
	p.saveError(err)
	if err != nil {
		return p
	}

	p.loggable = true
	p.populate(proc)

	return p
}

///////////////////////////////////////////////////////////////////////////
//
// message.Composer implementation
//
///////////////////////////////////////////////////////////////////////////

// Loggable returns true when the Processinfo structure has been
// populated.
func (p *ProcessInfo) Loggable() bool { return p.loggable }

// Raw always returns the ProcessInfo object, however it will call the
// Collect method of the base operation first.
func (p *ProcessInfo) Raw() interface{} { _ = p.Collect(); return p }

// String returns a string representation of the message, lazily
// rendering the message, and caching it privately.
func (p *ProcessInfo) String() string {
	if p.rendered != "" {
		return p.rendered
	}

	data, err := json.MarshalIndent(p, "  ", " ")
	if err != nil {
		return p.Message
	}

	if p.Message == "" {
		p.rendered = string(data)
	} else {
		p.rendered = fmt.Sprintf("%s:\n%s", p.Message, string(data))
	}

	return p.rendered
}

///////////////////////////////////////////////////////////////////////////
//
// Internal Methods for collecting data
//
///////////////////////////////////////////////////////////////////////////

func (p *ProcessInfo) populate(proc *process.Process) {
	var err error

	fmt.Println(p.Pid, proc.Pid)

	if p.Pid == 0 {
		p.Pid = proc.Pid
	}
	parentPid, err := proc.Ppid()
	p.saveError(err)
	if err == nil {
		p.Parent = parentPid
	}

	memInfo, err := proc.MemoryInfo()
	p.saveError(err)
	if err == nil {
		p.Memory = *memInfo
	}

	memInfoEx, err := proc.MemoryInfoEx()
	p.saveError(err)
	if err == nil {
		p.MemoryPlatform = *memInfoEx
	}

	threads, err := proc.NumThreads()
	p.Threads = int(threads)
	p.saveError(err)

	p.NetStat, err = proc.NetIOCounters(false)
	p.saveError(err)

	p.Command, err = proc.Cmdline()
	p.saveError(err)

	cpuTimes, err := proc.Times()
	p.saveError(err)
	if err == nil {
		p.CPU = *cpuTimes
	}

	ioStat, err := proc.IOCounters()
	p.saveError(err)
	if err == nil {
		p.IoStat = *ioStat
	}

}

func (p *ProcessInfo) saveError(err error) {
	if shouldSaveError(err) {
		p.Errors = append(p.Errors, err.Error())
	}
}
<commit_msg>remove print statement<commit_after>package message

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/shirou/gopsutil/cpu"
	"github.com/shirou/gopsutil/net"
	"github.com/shirou/gopsutil/process"
	"github.com/tychoish/grip/level"
)

// ProcessInfo holds the data for per-process statistics (e.g. cpu,
// memory, io). The Process info composers produce messages in this
// form.
type ProcessInfo struct {
	Message        string                   `json:"message,omitempty" bson:"message,omitempty"`
	Pid            int32                    `json:"pid" bson:"pid"`
	Parent         int32                    `json:"parentPid,omitempty" bson:"parentPid,omitempty"`
	Threads        int                      `json:"numThreads,omitempty" bson:"numThreads,omitempty"`
	Command        string                   `json:"command,omitempty" bson:"command,omitempty"`
	CPU            cpu.TimesStat            `json:"cpu,omitempty" bson:"cpu,omitempty"`
	IoStat         process.IOCountersStat   `json:"io,omitempty" bson:"io,omitempty"`
	NetStat        []net.IOCountersStat     `json:"net,omitempty" bson:"net,omitempty"`
	Memory         process.MemoryInfoStat   `json:"mem,omitempty" bson:"mem,omitempty"`
	MemoryPlatform process.MemoryInfoExStat `json:"memExtra,omitempty" bson:"memExtra,omitempty"`
	Errors         []string                 `json:"errors,omitempty" bson:"errors,omitempty"`
	Base           `json:"metadata,omitempty" bson:"metadata,omitempty"`
	loggable       bool
	rendered       string
}

///////////////////////////////////////////////////////////////////////////
//
// Constructors
//
///////////////////////////////////////////////////////////////////////////

// CollectProcessInfo returns a populated ProcessInfo message.Composer
// instance for the specified pid.
func CollectProcessInfo(pid int32) Composer {
	return NewProcessInfo(level.Trace, pid, "")
}

// CollectProcessInfoSelf returns a populated ProcessInfo message.Composer
// for the pid of the current process.
func CollectProcessInfoSelf() Composer {
	return NewProcessInfo(level.Trace, int32(os.Getpid()), "")
}

// CollectProcessInfoSelfWithChildren returns a slice of populated
// ProcessInfo message.Composer instances for the current process and
// all children processes.
func CollectProcessInfoSelfWithChildren() []Composer {
	return CollectProcessInfoWithChildren(int32(os.Getpid()))
}

// CollectProcessInfoWithChildren returns a slice of populated
// ProcessInfo message.Composer instances for the process with the
// specified pid and all children processes for that process.
func CollectProcessInfoWithChildren(pid int32) []Composer {
	var results []Composer
	parent, err := process.NewProcess(pid)
	if err != nil {
		return results
	}

	parentMsg := &ProcessInfo{}
	parentMsg.loggable = true
	parentMsg.populate(parent)
	results = append(results, parentMsg)

	children, err := parent.Children()
	parentMsg.saveError(err)
	if err != nil {
		return results
	}

	for _, child := range children {
		cm := &ProcessInfo{}
		cm.loggable = true
		cm.populate(child)
		results = append(results, cm)
	}

	return results
}

// NewProcessInfo constructs a fully configured and populated
// Processinfo message.Composer instance for the specified process.
func NewProcessInfo(priority level.Priority, pid int32, message string) Composer {
	p := &ProcessInfo{
		Message: message,
		Pid:     pid,
	}

	if err := p.SetPriority(priority); err != nil {
		p.saveError(err)
		return p
	}

	proc, err := process.NewProcess(pid)
	p.saveError(err)
	if err != nil {
		return p
	}

	p.loggable = true
	p.populate(proc)

	return p
}

///////////////////////////////////////////////////////////////////////////
//
// message.Composer implementation
//
///////////////////////////////////////////////////////////////////////////

// Loggable returns true when the Processinfo structure has been
// populated.
func (p *ProcessInfo) Loggable() bool { return p.loggable }

// Raw always returns the ProcessInfo object, however it will call the
// Collect method of the base operation first.
func (p *ProcessInfo) Raw() interface{} { _ = p.Collect(); return p }

// String returns a string representation of the message, lazily
// rendering the message, and caching it privately.
func (p *ProcessInfo) String() string {
	if p.rendered != "" {
		return p.rendered
	}

	data, err := json.MarshalIndent(p, "  ", " ")
	if err != nil {
		return p.Message
	}

	if p.Message == "" {
		p.rendered = string(data)
	} else {
		p.rendered = fmt.Sprintf("%s:\n%s", p.Message, string(data))
	}

	return p.rendered
}

///////////////////////////////////////////////////////////////////////////
//
// Internal Methods for collecting data
//
///////////////////////////////////////////////////////////////////////////

func (p *ProcessInfo) populate(proc *process.Process) {
	var err error

	if p.Pid == 0 {
		p.Pid = proc.Pid
	}
	parentPid, err := proc.Ppid()
	p.saveError(err)
	if err == nil {
		p.Parent = parentPid
	}

	memInfo, err := proc.MemoryInfo()
	p.saveError(err)
	if err == nil {
		p.Memory = *memInfo
	}

	memInfoEx, err := proc.MemoryInfoEx()
	p.saveError(err)
	if err == nil {
		p.MemoryPlatform = *memInfoEx
	}

	threads, err := proc.NumThreads()
	p.Threads = int(threads)
	p.saveError(err)

	p.NetStat, err = proc.NetIOCounters(false)
	p.saveError(err)

	p.Command, err = proc.Cmdline()
	p.saveError(err)

	cpuTimes, err := proc.Times()
	p.saveError(err)
	if err == nil {
		p.CPU = *cpuTimes
	}

	ioStat, err := proc.IOCounters()
	p.saveError(err)
	if err == nil {
		p.IoStat = *ioStat
	}

}

func (p *ProcessInfo) saveError(err error) {
	if shouldSaveError(err) {
		p.Errors = append(p.Errors, err.Error())
	}
}
<|endoftext|>


<commit_before>// Copyright 2012 Samuel Stauffer. All rights reserved.
// Use of this source code is governed by a 3-clause BSD
// license that can be found in the LICENSE file.

package metrics

import (
	"math"
	"sort"
	"sync"
)

const (
	mpElemSize = 8 // sizeof int64
)

var (
	DefaultPrecision = Precision{0.0001, 1000 * 1000}
	DefaultMaxMemory = 4 * 1024
)

// Precision expresses the maximum epsilon tolerated for a typical size of input
type Precision struct {
	Episilon float64
	N        int
}

type mpHistogram struct {
	buffer     [][]int64
	bufferPool [2][]int64
	indices    []int
	count      int64
	sum        int64
	min        int64
	max        int64
	leafCount  int // number of elements in the bottom two leaves
	currentTop int
	rootWeight int
	bufferSize int
	maxDepth   int
	mutex      sync.RWMutex
}

// An implemenation of the Munro-Paterson approximate histogram algorithm adapted from:
// https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/stats/ApproximateHistogram.java
// http://szl.googlecode.com/svn-history/r36/trunk/src/emitters/szlquantile.cc
func NewMunroPatersonHistogram(bufSize, maxDepth int) Histogram {
	buffer := make([][]int64, maxDepth+1)
	for i := 0; i < len(buffer); i++ {
		buffer[i] = make([]int64, bufSize)
	}
	return &mpHistogram{
		buffer:     buffer,
		bufferPool: [2][]int64{make([]int64, bufSize), make([]int64, bufSize)},
		indices:    make([]int, maxDepth+1),
		rootWeight: 1,
		currentTop: 1,
		bufferSize: bufSize,
		maxDepth:   maxDepth,
	}
}

func NewDefaultMunroPatersonHistogram() Histogram {
	return NewMunroPatersonHistogramWithMaxMemory(DefaultMaxMemory)
}

func NewMunroPatersonHistogramWithMaxMemory(bytes int) Histogram {
	b := computeB(DefaultPrecision.Episilon, DefaultPrecision.N)
	bufSize := computeBufferSize(b, DefaultPrecision.N)
	maxDepth := computeMaxDepth(bytes, bufSize)
	return NewMunroPatersonHistogram(bufSize, maxDepth)
}

func NewMunroPatersonHistogramWithPrecision(p Precision) Histogram {
	b := computeB(p.Episilon, p.N)
	bufSize := computeBufferSize(b, p.N)
	return NewMunroPatersonHistogram(bufSize, b)
}

func (mp *mpHistogram) String() string {
	mp.mutex.RLock()
	js := histogramToJson(mp, DefaultPercentiles, DefaultPercentileNames)
	mp.mutex.RUnlock()
	return js
}

func (mp *mpHistogram) Clear() {
	mp.mutex.Lock()
	mp.count = 0
	mp.sum = 0
	mp.leafCount = 0
	mp.rootWeight = 1
	mp.min = 0
	mp.max = 0
	mp.mutex.Unlock()
}

func (mp *mpHistogram) Count() uint64 {
	mp.mutex.RLock()
	count := uint64(mp.count)
	mp.mutex.RUnlock()
	return count
}

func (mp *mpHistogram) Mean() float64 {
	mp.mutex.RLock()
	mean := float64(mp.sum) / float64(mp.count)
	mp.mutex.RUnlock()
	return mean
}

func (mp *mpHistogram) Sum() int64 {
	mp.mutex.RLock()
	sum := mp.sum
	mp.mutex.RUnlock()
	return sum
}

func (mp *mpHistogram) Min() int64 {
	mp.mutex.RLock()
	min := mp.min
	mp.mutex.RUnlock()
	return min
}

func (mp *mpHistogram) Max() int64 {
	mp.mutex.RLock()
	max := mp.max
	mp.mutex.RUnlock()
	return max
}

func (mp *mpHistogram) Percentiles(qs []float64) []int64 {
	mp.mutex.Lock()
	defer mp.mutex.Unlock()

	output := make([]int64, len(qs))
	if mp.count == 0 {
		return output
	}

	// the two leaves are the only buffer that can be partially filled
	buf0Size := mp.leafCount
	buf1Size := 0
	if mp.leafCount > mp.bufferSize {
		buf0Size = mp.bufferSize
		buf1Size = mp.leafCount - mp.bufferSize
	}

	sort.Sort(int64Slice(mp.buffer[0][:buf0Size]))
	sort.Sort(int64Slice(mp.buffer[1][:buf1Size]))

	indices := mp.indices
	for i := 0; i < len(indices); i++ {
		indices[i] = 0
	}
	sum := int64(0)
	io := 0
	floatCount := float64(mp.count)
	for io < len(output) {
		i := mp.smallest(buf0Size, buf1Size, indices)
		id := indices[i]
		indices[i]++
		sum += int64(mp.weight(i))
		for io < len(qs) && int64(qs[io]*floatCount) <= sum {
			output[io] = mp.buffer[i][id]
			io++
		}
	}
	return output
}

// Return the level of the smallest element (using the indices array 'ids'
// to track which elements have been already returned). Every buffers has
// already been sorted at this point.
func (mp *mpHistogram) smallest(buf0Size, buf1Size int, ids []int) int {
	smallest := int64(math.MaxInt64)
	id0 := ids[0]
	id1 := ids[1]
	iSmallest := 0

	if mp.leafCount > 0 && id0 < buf0Size {
		smallest = mp.buffer[0][id0]
	}
	if mp.leafCount > mp.bufferSize && id1 < buf1Size {
		x := mp.buffer[1][id1]
		if x < smallest {
			smallest = x
			iSmallest = 1
		}
	}
	for i := 2; i <= mp.currentTop; i++ {
		if !mp.isBufferEmpty(i) && ids[i] < mp.bufferSize {
			x := mp.buffer[i][ids[i]]
			if x < smallest {
				smallest = x
				iSmallest = i
			}
		}
	}
	return iSmallest
}

func (mp *mpHistogram) Update(x int64) {
	mp.mutex.Lock()
	// if the leaves of the tree are full, "collapse" recursively the tree
	if mp.leafCount == 2*mp.bufferSize {
		sort.Sort(int64Slice(mp.buffer[0]))
		sort.Sort(int64Slice(mp.buffer[1]))
		mp.recCollapse(mp.buffer[0], 1)
		mp.leafCount = 0
	}

	// Now we're sure there is space for adding x
	if mp.leafCount < mp.bufferSize {
		mp.buffer[0][mp.leafCount] = x
	} else {
		mp.buffer[1][mp.leafCount-mp.bufferSize] = x
	}
	mp.leafCount++
	if mp.count == 0 {
		mp.min = x
		mp.max = x
	} else {
		if x < mp.min {
			mp.min = x
		}
		if x > mp.max {
			mp.max = x
		}
	}
	mp.count++
	mp.sum += x
	mp.mutex.Unlock()
}

func (mp *mpHistogram) recCollapse(buf []int64, level int) {
	// if we reach the root, we can't add more buffer
	if level == mp.maxDepth {
		// weight() returns the weight of the root, in that case we need the
		// weight of merge result
		mergeWeight := 1 << (uint(level) - 1)
		idx := level & 1
		merged := mp.bufferPool[idx]
		tmp := mp.buffer[level]
		if mergeWeight == mp.rootWeight {
			mp.collapse1(buf, mp.buffer[level], merged)
		} else {
			mp.collapse(buf, mergeWeight, mp.buffer[level], mp.rootWeight, merged)
		}
		mp.buffer[level] = merged
		mp.bufferPool[idx] = tmp
		mp.rootWeight += mergeWeight
	} else {
		if level == mp.currentTop {
			// if we reach the top, add a new buffer
			mp.collapse1(buf, mp.buffer[level], mp.buffer[level+1])
			mp.currentTop++
			mp.rootWeight *= 2
		} else if mp.isBufferEmpty(level + 1) {
			// if the upper buffer is empty, use it
			mp.collapse1(buf, mp.buffer[level], mp.buffer[level+1])
		} else {
			// if the upper buffer isn't empty, collapse with it
			merged := mp.bufferPool[level&1]
			mp.collapse1(buf, mp.buffer[level], merged)
			mp.recCollapse(merged, level+1)
		}
	}
}

// collapse two sorted Arrays of different weight
// ex: [2,5,7] weight 2 and [3,8,9] weight 3
//     weight x array + concat = [2,2,5,5,7,7,3,3,3,8,8,8,9,9,9]
//     sort = [2,2,3,3,3,5,5,7,7,8,8,8,9,9,9]
//     select every nth elems = [3,7,9]  (n = sum weight / 2)
func (mp *mpHistogram) collapse(left []int64, leftWeight int, right []int64, rightWeight int, output []int64) {
	totalWeight := leftWeight + rightWeight
	cnt0 := -totalWeight / 2
	cnt1 := totalWeight + cnt0
	i := 0
	j := 0
	k := 0
	cnt := cnt0

	var smallest int64
	var weight int

	for i < len(left) || j < len(right) {
		if i < len(left) && (j == len(right) || left[i] < right[j]) {
			smallest = left[i]
			weight = leftWeight
			i++
		} else {
			smallest = right[j]
			weight = rightWeight
			j++
		}

		if cnt <= 0 && cnt+weight > 0 {
			output[k] = smallest
			k++
		}

		cnt += weight
		if cnt >= cnt1 {
			cnt -= totalWeight
			if cnt > 0 {
				output[k] = smallest
				k++
			}
		}
	}
}

// Optimized version of collapse for collapsing two arrays of the
// same weight (which is what we want most of the time)
func (mp *mpHistogram) collapse1(left, right, output []int64) {
	i, j, k, cnt := 0, 0, 0, 0
	ll := len(left)
	lr := len(right)
	for i < ll || j < lr {
		var smallest int64
		if i < ll && (j == lr || left[i] < right[j]) {
			smallest = left[i]
			i++
		} else {
			smallest = right[j]
			j++
		}
		if cnt&1 == 1 {
			output[k] = smallest
			k++
		}
		cnt++
	}
}

func (mp *mpHistogram) isBufferEmpty(level int) bool {
	if level == mp.currentTop {
		return false // root buffer (is present) is always full
	}
	return (mp.count/int64(mp.bufferSize*mp.weight(level)))&1 == 1
}

// return the weight of the level ie. 2^(i-1) except for the two tree
// leaves (weight=1) and for the root
func (mp *mpHistogram) weight(level int) int {
	if level < 2 {
		return 1
	}
	if level == mp.maxDepth {
		return mp.rootWeight
	}
	return 1 << (uint(level) - 1)
}

//

// We compute the "smallest possible k" satisfying two inequalities:
//    1)   (b - 2) * (2 ^ (b - 2)) + 0.5 <= epsilon * N
//    2)   k * (2 ^ (b - 1)) >= N
//
// For an explanation of these inequalities, please read the Munro-Paterson or
// the Manku-Rajagopalan-Linday papers.
func computeB(epsilon float64, n int) int {
	b := uint(2)
	en := epsilon * float64(n)
	for float64((b-2)*(1<<(b-2)))+0.5 <= en {
		b++
	}
	return int(b)
}

func computeBufferSize(b int, n int) int {
	return int(n / (1 << (uint(b) - 1)))
}

// Return the maximum depth of the graph to comply with the memory constraint
func computeMaxDepth(maxMemoryBytes int, bufferSize int) int {
	bm := 0
	n := maxMemoryBytes - 100 - mpElemSize*bufferSize
	if n < 0 {
		bm = 2
	} else {
		bm = int(n / (16 + mpElemSize*bufferSize))
	}
	if bm < 2 {
		bm = 2
	}
	return bm
}
<commit_msg>Remove locking in mpHistogram.String since it'll happen in the all the subsequent calls<commit_after>// Copyright 2012 Samuel Stauffer. All rights reserved.
// Use of this source code is governed by a 3-clause BSD
// license that can be found in the LICENSE file.

package metrics

import (
	"math"
	"sort"
	"sync"
)

const (
	mpElemSize = 8 // sizeof int64
)

var (
	DefaultPrecision = Precision{0.0001, 1000 * 1000}
	DefaultMaxMemory = 4 * 1024
)

// Precision expresses the maximum epsilon tolerated for a typical size of input
type Precision struct {
	Episilon float64
	N        int
}

type mpHistogram struct {
	buffer     [][]int64
	bufferPool [2][]int64
	indices    []int
	count      int64
	sum        int64
	min        int64
	max        int64
	leafCount  int // number of elements in the bottom two leaves
	currentTop int
	rootWeight int
	bufferSize int
	maxDepth   int
	mutex      sync.RWMutex
}

// An implemenation of the Munro-Paterson approximate histogram algorithm adapted from:
// https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/stats/ApproximateHistogram.java
// http://szl.googlecode.com/svn-history/r36/trunk/src/emitters/szlquantile.cc
func NewMunroPatersonHistogram(bufSize, maxDepth int) Histogram {
	buffer := make([][]int64, maxDepth+1)
	for i := 0; i < len(buffer); i++ {
		buffer[i] = make([]int64, bufSize)
	}
	return &mpHistogram{
		buffer:     buffer,
		bufferPool: [2][]int64{make([]int64, bufSize), make([]int64, bufSize)},
		indices:    make([]int, maxDepth+1),
		rootWeight: 1,
		currentTop: 1,
		bufferSize: bufSize,
		maxDepth:   maxDepth,
	}
}

func NewDefaultMunroPatersonHistogram() Histogram {
	return NewMunroPatersonHistogramWithMaxMemory(DefaultMaxMemory)
}

func NewMunroPatersonHistogramWithMaxMemory(bytes int) Histogram {
	b := computeB(DefaultPrecision.Episilon, DefaultPrecision.N)
	bufSize := computeBufferSize(b, DefaultPrecision.N)
	maxDepth := computeMaxDepth(bytes, bufSize)
	return NewMunroPatersonHistogram(bufSize, maxDepth)
}

func NewMunroPatersonHistogramWithPrecision(p Precision) Histogram {
	b := computeB(p.Episilon, p.N)
	bufSize := computeBufferSize(b, p.N)
	return NewMunroPatersonHistogram(bufSize, b)
}

func (mp *mpHistogram) String() string {
	return histogramToJson(mp, DefaultPercentiles, DefaultPercentileNames)
}

func (mp *mpHistogram) Clear() {
	mp.mutex.Lock()
	mp.count = 0
	mp.sum = 0
	mp.leafCount = 0
	mp.rootWeight = 1
	mp.min = 0
	mp.max = 0
	mp.mutex.Unlock()
}

func (mp *mpHistogram) Count() uint64 {
	mp.mutex.RLock()
	count := uint64(mp.count)
	mp.mutex.RUnlock()
	return count
}

func (mp *mpHistogram) Mean() float64 {
	mp.mutex.RLock()
	mean := float64(mp.sum) / float64(mp.count)
	mp.mutex.RUnlock()
	return mean
}

func (mp *mpHistogram) Sum() int64 {
	mp.mutex.RLock()
	sum := mp.sum
	mp.mutex.RUnlock()
	return sum
}

func (mp *mpHistogram) Min() int64 {
	mp.mutex.RLock()
	min := mp.min
	mp.mutex.RUnlock()
	return min
}

func (mp *mpHistogram) Max() int64 {
	mp.mutex.RLock()
	max := mp.max
	mp.mutex.RUnlock()
	return max
}

func (mp *mpHistogram) Percentiles(qs []float64) []int64 {
	mp.mutex.Lock()
	defer mp.mutex.Unlock()

	output := make([]int64, len(qs))
	if mp.count == 0 {
		return output
	}

	// the two leaves are the only buffer that can be partially filled
	buf0Size := mp.leafCount
	buf1Size := 0
	if mp.leafCount > mp.bufferSize {
		buf0Size = mp.bufferSize
		buf1Size = mp.leafCount - mp.bufferSize
	}

	sort.Sort(int64Slice(mp.buffer[0][:buf0Size]))
	sort.Sort(int64Slice(mp.buffer[1][:buf1Size]))

	indices := mp.indices
	for i := 0; i < len(indices); i++ {
		indices[i] = 0
	}
	sum := int64(0)
	io := 0
	floatCount := float64(mp.count)
	for io < len(output) {
		i := mp.smallest(buf0Size, buf1Size, indices)
		id := indices[i]
		indices[i]++
		sum += int64(mp.weight(i))
		for io < len(qs) && int64(qs[io]*floatCount) <= sum {
			output[io] = mp.buffer[i][id]
			io++
		}
	}
	return output
}

// Return the level of the smallest element (using the indices array 'ids'
// to track which elements have been already returned). Every buffers has
// already been sorted at this point.
func (mp *mpHistogram) smallest(buf0Size, buf1Size int, ids []int) int {
	smallest := int64(math.MaxInt64)
	id0 := ids[0]
	id1 := ids[1]
	iSmallest := 0

	if mp.leafCount > 0 && id0 < buf0Size {
		smallest = mp.buffer[0][id0]
	}
	if mp.leafCount > mp.bufferSize && id1 < buf1Size {
		x := mp.buffer[1][id1]
		if x < smallest {
			smallest = x
			iSmallest = 1
		}
	}
	for i := 2; i <= mp.currentTop; i++ {
		if !mp.isBufferEmpty(i) && ids[i] < mp.bufferSize {
			x := mp.buffer[i][ids[i]]
			if x < smallest {
				smallest = x
				iSmallest = i
			}
		}
	}
	return iSmallest
}

func (mp *mpHistogram) Update(x int64) {
	mp.mutex.Lock()
	// if the leaves of the tree are full, "collapse" recursively the tree
	if mp.leafCount == 2*mp.bufferSize {
		sort.Sort(int64Slice(mp.buffer[0]))
		sort.Sort(int64Slice(mp.buffer[1]))
		mp.recCollapse(mp.buffer[0], 1)
		mp.leafCount = 0
	}

	// Now we're sure there is space for adding x
	if mp.leafCount < mp.bufferSize {
		mp.buffer[0][mp.leafCount] = x
	} else {
		mp.buffer[1][mp.leafCount-mp.bufferSize] = x
	}
	mp.leafCount++
	if mp.count == 0 {
		mp.min = x
		mp.max = x
	} else {
		if x < mp.min {
			mp.min = x
		}
		if x > mp.max {
			mp.max = x
		}
	}
	mp.count++
	mp.sum += x
	mp.mutex.Unlock()
}

func (mp *mpHistogram) recCollapse(buf []int64, level int) {
	// if we reach the root, we can't add more buffer
	if level == mp.maxDepth {
		// weight() returns the weight of the root, in that case we need the
		// weight of merge result
		mergeWeight := 1 << (uint(level) - 1)
		idx := level & 1
		merged := mp.bufferPool[idx]
		tmp := mp.buffer[level]
		if mergeWeight == mp.rootWeight {
			mp.collapse1(buf, mp.buffer[level], merged)
		} else {
			mp.collapse(buf, mergeWeight, mp.buffer[level], mp.rootWeight, merged)
		}
		mp.buffer[level] = merged
		mp.bufferPool[idx] = tmp
		mp.rootWeight += mergeWeight
	} else {
		if level == mp.currentTop {
			// if we reach the top, add a new buffer
			mp.collapse1(buf, mp.buffer[level], mp.buffer[level+1])
			mp.currentTop++
			mp.rootWeight *= 2
		} else if mp.isBufferEmpty(level + 1) {
			// if the upper buffer is empty, use it
			mp.collapse1(buf, mp.buffer[level], mp.buffer[level+1])
		} else {
			// if the upper buffer isn't empty, collapse with it
			merged := mp.bufferPool[level&1]
			mp.collapse1(buf, mp.buffer[level], merged)
			mp.recCollapse(merged, level+1)
		}
	}
}

// collapse two sorted Arrays of different weight
// ex: [2,5,7] weight 2 and [3,8,9] weight 3
//     weight x array + concat = [2,2,5,5,7,7,3,3,3,8,8,8,9,9,9]
//     sort = [2,2,3,3,3,5,5,7,7,8,8,8,9,9,9]
//     select every nth elems = [3,7,9]  (n = sum weight / 2)
func (mp *mpHistogram) collapse(left []int64, leftWeight int, right []int64, rightWeight int, output []int64) {
	totalWeight := leftWeight + rightWeight
	cnt0 := -totalWeight / 2
	cnt1 := totalWeight + cnt0
	i := 0
	j := 0
	k := 0
	cnt := cnt0

	var smallest int64
	var weight int

	for i < len(left) || j < len(right) {
		if i < len(left) && (j == len(right) || left[i] < right[j]) {
			smallest = left[i]
			weight = leftWeight
			i++
		} else {
			smallest = right[j]
			weight = rightWeight
			j++
		}

		if cnt <= 0 && cnt+weight > 0 {
			output[k] = smallest
			k++
		}

		cnt += weight
		if cnt >= cnt1 {
			cnt -= totalWeight
			if cnt > 0 {
				output[k] = smallest
				k++
			}
		}
	}
}

// Optimized version of collapse for collapsing two arrays of the
// same weight (which is what we want most of the time)
func (mp *mpHistogram) collapse1(left, right, output []int64) {
	i, j, k, cnt := 0, 0, 0, 0
	ll := len(left)
	lr := len(right)
	for i < ll || j < lr {
		var smallest int64
		if i < ll && (j == lr || left[i] < right[j]) {
			smallest = left[i]
			i++
		} else {
			smallest = right[j]
			j++
		}
		if cnt&1 == 1 {
			output[k] = smallest
			k++
		}
		cnt++
	}
}

func (mp *mpHistogram) isBufferEmpty(level int) bool {
	if level == mp.currentTop {
		return false // root buffer (is present) is always full
	}
	return (mp.count/int64(mp.bufferSize*mp.weight(level)))&1 == 1
}

// return the weight of the level ie. 2^(i-1) except for the two tree
// leaves (weight=1) and for the root
func (mp *mpHistogram) weight(level int) int {
	if level < 2 {
		return 1
	}
	if level == mp.maxDepth {
		return mp.rootWeight
	}
	return 1 << (uint(level) - 1)
}

//

// We compute the "smallest possible k" satisfying two inequalities:
//    1)   (b - 2) * (2 ^ (b - 2)) + 0.5 <= epsilon * N
//    2)   k * (2 ^ (b - 1)) >= N
//
// For an explanation of these inequalities, please read the Munro-Paterson or
// the Manku-Rajagopalan-Linday papers.
func computeB(epsilon float64, n int) int {
	b := uint(2)
	en := epsilon * float64(n)
	for float64((b-2)*(1<<(b-2)))+0.5 <= en {
		b++
	}
	return int(b)
}

func computeBufferSize(b int, n int) int {
	return int(n / (1 << (uint(b) - 1)))
}

// Return the maximum depth of the graph to comply with the memory constraint
func computeMaxDepth(maxMemoryBytes int, bufferSize int) int {
	bm := 0
	n := maxMemoryBytes - 100 - mpElemSize*bufferSize
	if n < 0 {
		bm = 2
	} else {
		bm = int(n / (16 + mpElemSize*bufferSize))
	}
	if bm < 2 {
		bm = 2
	}
	return bm
}
<|endoftext|>